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()!!
}