fun createDebugProcess()

in plugins/toolkit/jetbrains-rider/src/software/aws/toolkits/jetbrains/services/lambda/dotnet/DotnetDebugUtils.kt [61:195]


    fun createDebugProcess(
        environment: ExecutionEnvironment,
        debugHost: String,
        debugPorts: List<Int>,
        context: Context,
    ): XDebugProcessStarter {
        val frontendPort = debugPorts[0]
        val backendPort = debugPorts[1]
        val promise = AsyncPromise<XDebugProcessStarter>()
        val edtContext = getCoroutineUiContext()
        val bgContext = getCoroutineBgContext()

        // Define a debugger lifetime to be able to dispose the debugger process and all nested component on termination
        // Using the environment as the disposable root seems to make 2nd usage of debug to fail since the lifetime is already terminated
        val debuggerLifetimeDefinition = environment.project.defineNestedLifetime()
        val debuggerLifetime = debuggerLifetimeDefinition.lifetime

        val scheduler = RdDispatcher(debuggerLifetime)

        val debugHostAddress = InetAddress.getByName(debugHost)

        val protocol = Protocol(
            name = environment.runProfile.name,
            serializers = Serializers(),
            identity = Identities(IdKind.Client),
            scheduler = scheduler,
            wire = SocketWire.Client(debuggerLifetime, scheduler, hostAddress = debugHostAddress, port = frontendPort, optId = "FrontendToDebugWorker"),
            lifetime = debuggerLifetime
        )

        val workerModel = runBlocking(edtContext) {
            RiderDebuggerWorkerModelManager.createDebuggerModel(debuggerLifetime, protocol)
        }

        disposableCoroutineScope(environment, environment.runProfile.name).launch(bgContext) {
            try {
                val dockerContainer = context.getRequiredAttribute(DOCKER_CONTAINER)
                val pid = context.getRequiredAttribute(DOTNET_PID)
                val riderDebuggerProcessHandler = startDebugWorker(dockerContainer, backendPort, frontendPort)
                riderDebuggerProcessHandler.addProcessListener(object : CapturingProcessAdapter() {
                    override fun processTerminated(event: ProcessEvent) {
                        super.processTerminated(event)
                        // if we don't get this message, we can assume the debugger terminated prematurely
                        // TODO: can we detect this without waiting for the timeout task to kick in?
                        LOG.debug {
                            """
                                Rider debugger worker exited with code: ${output.exitCode}
                                stdout: ${output.stdout}
                                stderr: ${output.stderr}
                            """.trimIndent()
                        }
                    }
                })

                LOG.debug { "Waiting for debug process worker to be available" }

                withContext(edtContext) {
                    protocol.wire.connected.adviseUntil(debuggerLifetime) connected@{ isConnected ->
                        if (!isConnected) {
                            return@connected false
                        }

                        workerModel.initialized.adviseUntil(debuggerLifetime) initialized@{ isInitialized ->
                            if (!isInitialized) {
                                return@initialized false
                            }

                            // Fire backend to connect to debugger.
                            environment.project.solution.debuggerWorkerConnectionHelperModel.ports.put(
                                debuggerLifetime,
                                environment.executionId,
                                backendPort
                            )

                            val startInfo = DotNetCoreAttachStartInfo(
                                processId = pid
                            )

                            val sessionModel = DotNetDebuggerSessionModel(startInfo)
                            sessionModel.sessionProperties.bindToSettings(debuggerLifetime, environment.project).apply {
                                enableHeuristicPathResolve.set(true)
                            }

                            workerModel.activeSession.set(sessionModel)

                            val console = TextConsoleBuilderFactory.getInstance().createBuilder(environment.project).console

                            promise.setResult(
                                DotNetDebuggerUtils.createAndStartSession(
                                    executionConsole = console,
                                    env = environment,
                                    sessionLifetime = debuggerLifetime,
                                    processHandler = DebuggerWorkerProcessHandler(
                                        riderDebuggerProcessHandler,
                                        workerModel,
                                        true,
                                        riderDebuggerProcessHandler.commandLine,
                                        debuggerLifetime
                                    ),
                                    protocol = protocol,
                                    sessionModel = sessionModel,
                                    outputEventsListener = object : IDebuggerOutputListener {}
                                )
                            )

                            return@initialized true
                        }

                        return@connected true
                    }
                }
            } catch (t: Throwable) {
                LOG.warn(t) { "Failed to start debugger" }
                debuggerLifetimeDefinition.terminate(true)
                withContext(edtContext) {
                    promise.setError(t)
                }
            }
        }

        val checkDebuggerTask = Timer("Debugger Worker launch timer", true).schedule(SamDebugSupport.debuggerConnectTimeoutMs()) {
            if (debuggerLifetimeDefinition.isAlive && !protocol.wire.connected.value) {
                runBlocking(edtContext) {
                    debuggerLifetimeDefinition.terminate()
                    promise.setError(message("lambda.debug.process.start.timeout"))
                }
            }
        }

        debuggerLifetime.onTermination {
            checkDebuggerTask.cancel()
        }

        return promise.get()!!
    }