mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Fix PY-20831: Do not let console lock up UI thread for a long time
This commit is contained in:
@@ -19,4 +19,7 @@ public interface IPydevXmlRpcClient {
|
||||
* @throws XmlRpcException
|
||||
*/
|
||||
Object execute(String command, Object[] args) throws XmlRpcException;
|
||||
|
||||
Object execute(String command, Object[] args, long timeoutMillis) throws XmlRpcException;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
package com.jetbrains.python.console.pydev;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.util.net.NetUtils;
|
||||
import org.apache.xmlrpc.*;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Subclass of XmlRpcClient that will monitor the process so that if the process is destroyed, we stop waiting
|
||||
* for messages from it.
|
||||
*
|
||||
* @author Fabio
|
||||
*/
|
||||
public class PydevXmlRpcClient implements IPydevXmlRpcClient {
|
||||
|
||||
/**
|
||||
* Internal xml-rpc client (responsible for the actual communication with the server)
|
||||
*/
|
||||
private final XmlRpcClient impl;
|
||||
|
||||
/**
|
||||
* The process where the server is being executed.
|
||||
*/
|
||||
private final Process process;
|
||||
|
||||
/**
|
||||
* ItelliJ Logging
|
||||
*/
|
||||
private static final Logger LOG = Logger.getInstance(PydevXmlRpcClient.class.getName());
|
||||
|
||||
private static final long TIME_LIMIT = 60000;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor (see fields description)
|
||||
*/
|
||||
public PydevXmlRpcClient(Process process, int port) throws MalformedURLException {
|
||||
XmlRpc.setDefaultInputEncoding("UTF8"); //even though it uses UTF anyway
|
||||
impl = new XmlRpcClientLite(NetUtils.getLocalHostString(), port);
|
||||
//this.impl = new XmlRpcClient(url, new CommonsXmlRpcTransportFactory(url));
|
||||
this.process = process;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a command in the server.
|
||||
* <p/>
|
||||
* Within this method, we should be careful about being able to return if the server dies.
|
||||
* If we wanted to have a timeout, this would be the place to add it.
|
||||
*
|
||||
* @return the result from executing the given command in the server.
|
||||
*/
|
||||
@Override
|
||||
public Object execute(String command, Object[] args) throws XmlRpcException {
|
||||
final Object[] result = new Object[]{null};
|
||||
|
||||
//make an async call so that we can keep track of not actually having an answer.
|
||||
impl.executeAsync(command, new Vector(Arrays.asList(args)), new AsyncCallback() {
|
||||
|
||||
@Override
|
||||
public void handleError(Exception error, URL url, String method) {
|
||||
result[0] = new Object[]{error.getMessage()};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleResult(Object recievedResult, URL url, String method) {
|
||||
result[0] = recievedResult;
|
||||
}
|
||||
});
|
||||
|
||||
long started = System.currentTimeMillis();
|
||||
//busy loop waiting for the answer (or having the console die).
|
||||
while (result[0] == null && System.currentTimeMillis() - started < TIME_LIMIT) {
|
||||
try {
|
||||
if (process != null) {
|
||||
int exitValue = process.exitValue();
|
||||
result[0] = new Object[]{String.format("Console already exited with value: %s while waiting for an answer.\n", exitValue)};
|
||||
//ok, we have an exit value!
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (IllegalThreadStateException e) {
|
||||
//that's ok... let's sleep a bit
|
||||
synchronized (this) {
|
||||
try {
|
||||
wait(10);
|
||||
}
|
||||
catch (InterruptedException e1) {
|
||||
LOG.error(e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result[0] == null) {
|
||||
throw new XmlRpcException(-1, "Timeout while connecting to server");
|
||||
}
|
||||
return result[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.jetbrains.python.console.pydev
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.util.net.NetUtils
|
||||
import org.apache.xmlrpc.*
|
||||
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
import java.util.Arrays
|
||||
import java.util.Vector
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Subclass of XmlRpcClient that will monitor the process so that if the process is destroyed, we stop waiting
|
||||
* for messages from it.
|
||||
|
||||
* @author Fabio
|
||||
*/
|
||||
class PydevXmlRpcClient
|
||||
/**
|
||||
* Constructor (see fields description)
|
||||
*/
|
||||
@Throws(MalformedURLException::class)
|
||||
constructor(private val process: Process, port: Int) : IPydevXmlRpcClient {
|
||||
|
||||
|
||||
/**
|
||||
* Internal xml-rpc client (responsible for the actual communication with the server)
|
||||
*/
|
||||
private val impl: XmlRpcClient
|
||||
|
||||
|
||||
init {
|
||||
XmlRpc.setDefaultInputEncoding("UTF8") //even though it uses UTF anyway
|
||||
impl = XmlRpcClientLite(NetUtils.getLocalHostString(), port)
|
||||
}
|
||||
|
||||
|
||||
override fun execute(command: String, args: Array<Any>): Any {
|
||||
return execute(command, args, TIME_LIMIT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a command in the server.
|
||||
*
|
||||
*
|
||||
* Within this method, we should be careful about being able to return if the server dies.
|
||||
* If we wanted to have a timeout, this would be the place to add it.
|
||||
|
||||
* @return the result from executing the given command in the server.
|
||||
*/
|
||||
@Throws(XmlRpcException::class)
|
||||
override fun execute(command: String, args: Array<Any>, timeoutMillis: Long): Any {
|
||||
val result = arrayOf<Any?>(null)
|
||||
|
||||
//make an async call so that we can keep track of not actually having an answer.
|
||||
impl.executeAsync(command, Vector(Arrays.asList(*args)), object : AsyncCallback {
|
||||
|
||||
override fun handleError(error: Exception, url: URL, method: String) {
|
||||
result[0] = makeError(error.message ?: "Unknown Error")
|
||||
}
|
||||
|
||||
override fun handleResult(recievedResult: Any, url: URL, method: String) {
|
||||
result[0] = recievedResult
|
||||
}
|
||||
})
|
||||
|
||||
val started = System.currentTimeMillis()
|
||||
val progress = ProgressManager.getInstance().progressIndicator
|
||||
//busy loop waiting for the answer (or having the console die).
|
||||
while (result[0] == null && System.currentTimeMillis() - started < TIME_LIMIT) {
|
||||
|
||||
progress?.let {
|
||||
progress.checkCanceled()
|
||||
}
|
||||
val exitValue = process.waitFor(10, TimeUnit.MILLISECONDS)
|
||||
if (exitValue) {
|
||||
result[0] = makeError(String.format("Console already exited with value: %s while waiting for an answer.\n", exitValue))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result[0] ?: throw XmlRpcException(-1, "Timeout while connecting to server")
|
||||
|
||||
}
|
||||
|
||||
fun makeError(error: String): Array<Any> {
|
||||
return arrayOf(error)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* ItelliJ Logging
|
||||
*/
|
||||
private val LOG = Logger.getInstance(PydevXmlRpcClient::class.java.name)
|
||||
|
||||
private val TIME_LIMIT: Long = 60000
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,6 @@
|
||||
<orderEntry type="library" name="Guava" level="project" />
|
||||
<orderEntry type="library" name="xpp3-1.1.4-min" level="project" />
|
||||
<orderEntry type="module" module-name="util" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
</module>
|
||||
@@ -137,7 +137,7 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl
|
||||
*/
|
||||
public synchronized void close() {
|
||||
if (this.myClient != null) {
|
||||
new Task.Backgroundable(myProject, "Close console communication", true) {
|
||||
new Task.Backgroundable(myProject, "Close Console Communication", true) {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
try {
|
||||
@@ -326,7 +326,7 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl
|
||||
if (waitingForInput) {
|
||||
return "Unable to get description: waiting for input.";
|
||||
}
|
||||
return myClient.execute(GET_DESCRIPTION, new Object[]{text}).toString();
|
||||
return myClient.execute(GET_DESCRIPTION, new Object[]{text}, 5000).toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -913,8 +913,12 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
if (myProcessHandler != null) {
|
||||
UIUtil.invokeLaterIfNeeded(() -> closeCommunication());
|
||||
UIUtil.invokeAndWaitIfNeeded((Runnable)() -> closeCommunication());
|
||||
|
||||
boolean processStopped = myProcessHandler.waitFor(5000L);
|
||||
if (!processStopped && myProcessHandler.canKillProcess()) {
|
||||
myProcessHandler.killProcess();
|
||||
}
|
||||
myProcessHandler.waitFor();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user