Python: kill process when scope is cancelled.

Previous implementation wasn't synchronized leaving space for a race.

GitOrigin-RevId: 00231be95ac33daf27bb8fe7a814c9f2fcd89f08
This commit is contained in:
Ilya.Kazakevich
2025-07-02 04:33:27 +02:00
committed by intellij-monorepo-bot
parent 2fa873cff7
commit 92d8c4f34c
2 changed files with 35 additions and 39 deletions

View File

@@ -16,10 +16,7 @@ import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.ProcessInteractiveHandler
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.*
import org.jetbrains.annotations.CheckReturnValue
import org.jetbrains.annotations.Nls
import java.nio.file.Path
@@ -36,26 +33,41 @@ internal object ExecServiceImpl : ExecService {
val description = options.processDescription
?: PyExecBundle.message("py.exec.defaultName.process", (listOf(binary.pathString) + args).joinToString(" "))
val eelPath = binary.asEelPath()
val executableProcess = EelExecutableProcess(eelPath, args, options.env, options.workingDirectory, description)
val eelProcess = executableProcess.run().getOr { return it }
return coroutineScope {
val result = try {
withTimeout(options.timeout) {
val interactiveResult = processInteractiveHandler.getResultFromProcess(binary, args, eelProcess)
val successResult = interactiveResult.getOr { failure ->
val (output, customErrorMessage) = failure.error
return@withTimeout executableProcess.failAsExecutionFailed(ExecErrorReason.UnexpectedProcessTermination(output), customErrorMessage)
val eelPath = binary.asEelPath()
val executableProcess = EelExecutableProcess(eelPath, args, options.env, options.workingDirectory, description)
val eelProcess = executableProcess.run().getOr { return@coroutineScope it }
launch(start = CoroutineStart.UNDISPATCHED) {
try {
eelProcess.exitCode.await()
}
catch (e: CancellationException) {
withContext(NonCancellable) {
eelProcess.kill()
eelProcess.exitCode.await()
}
throw e
}
Result.success(successResult)
}
}
catch (_: TimeoutCancellationException) {
executableProcess.killProcessAndFailAsTimeout(eelProcess, options.timeout)
}
return result
val result = try {
withTimeout(options.timeout) {
val interactiveResult = processInteractiveHandler.getResultFromProcess(binary, args, eelProcess)
val successResult = interactiveResult.getOr { failure ->
val (output, customErrorMessage) = failure.error
return@withTimeout executableProcess.failAsExecutionFailed(ExecErrorReason.UnexpectedProcessTermination(output), customErrorMessage)
}
Result.success(successResult)
}
}
catch (_: TimeoutCancellationException) {
executableProcess.killProcessAndFailAsTimeout(eelProcess, options.timeout)
}
return@coroutineScope result
}
}
}
@@ -93,10 +105,12 @@ private fun EelExecutableProcess.failAsCantStart(executeProcessError: ExecutePro
}
private suspend fun EelExecutableProcess.killProcessAndFailAsTimeout(eelProcess: EelProcess, timeout: Duration): Result.Failure<ExecError> {
eelProcess.interrupt()
eelProcess.kill()
eelProcess.exitCode.await()
return ExecError(
exe = Exe.OnEel( exe),
exe = Exe.OnEel(exe),
args = args.toTypedArray(),
additionalMessageToUser = PyExecBundle.message("py.exec.timeout.error", description, timeout),
errorReason = ExecErrorReason.Timeout

View File

@@ -17,24 +17,6 @@ import kotlin.time.Duration.Companion.seconds
*/
internal suspend fun EelProcess.awaitWithReporting(progressListener: FlowCollector<ProcessEvent.ProcessOutput>?): EelProcessExecutionResult =
coroutineScope {
launch {
// As we read process in blocking manner, we might freeze forever
// Here we check if coroutine was killed to kill process as well
try {
exitCode.await()
}
catch (e: CancellationException) {
withContext(NonCancellable) {
interrupt()
withTimeoutOrNull(1.seconds) {
exitCode.await()
}
kill()
exitCode.await()
}
throw e
}
}
val stdout = async { report(STDOUT, progressListener) }
val stderr = async { report(STDERR, progressListener) }
EelProcessExecutionResult(exitCode.await(), stdout = stdout.await(), stderr = stderr.await())