diff --git a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java index 7767701ea425..49a2d35469ff 100644 --- a/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PydevConsoleCommunication.java @@ -56,7 +56,7 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl /** * Thrift RPC client for sending messages to the server. */ - private PythonConsoleBackendService.Iface myClient; + private PythonConsoleBackendServiceDisposable myClient; /** * This is the server responsible for giving input to a raw_input() requested. @@ -208,17 +208,18 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl } /** - * Returns thread safe {@link PythonConsoleBackendService.Iface}: requests to - * the returned {@link PythonConsoleBackendService.Iface} will be processed - * sequentially. Also if Python Console process is detected to be finished - * the current request will be interrupted with - * {@link PyConsoleProcessFinishedException}. + * Returns thread safe, Python Console process-aware and disposable + * {@link PythonConsoleBackendService.Iface}. Requests to the returned + * {@link PythonConsoleBackendService.Iface} will be processed sequentially. + * If Python Console process is detected to be finished the current request + * will be interrupted and {@link PyConsoleProcessFinishedException} is + * thrown. * * @return thread safe and related Python Console process-aware * {@link PythonConsoleBackendService.Iface} */ @NotNull - private PythonConsoleBackendService.Iface getPythonConsoleBackendClient() { + private PythonConsoleBackendServiceDisposable getPythonConsoleBackendClient() { if (myClient == null) { myClient = PythonConsoleClientUtil.synchronizedPythonConsoleClient(PydevConsoleCommunication.class.getClassLoader(), getInitialPythonConsoleBackendClient(), getPythonConsoleProcess()); @@ -250,6 +251,9 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl catch (Exception e) { //Ok, we can ignore this one on close. } + finally { + getPythonConsoleBackendClient().dispose(); + } } }.queue(); } @@ -278,6 +282,9 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl catch (Exception e) { //Ok, we can ignore this one on close. } + finally { + getPythonConsoleBackendClient().dispose(); + } indicator.setText2("Waiting for Python Console process to finish..."); try { do { @@ -319,8 +326,9 @@ public class PydevConsoleCommunication extends AbstractConsoleCommunication impl } finally { if (myServer != null) { - myServer.stop(); + Future stopFuture = myServer.stop(); myServer = null; + stopFuture.get(); } } return null; diff --git a/python/src/com/jetbrains/python/console/PythonConsoleBackendServiceDisposable.kt b/python/src/com/jetbrains/python/console/PythonConsoleBackendServiceDisposable.kt new file mode 100644 index 000000000000..de8d383e8564 --- /dev/null +++ b/python/src/com/jetbrains/python/console/PythonConsoleBackendServiceDisposable.kt @@ -0,0 +1,10 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.console + +/** + * Not really [com.intellij.openapi.Disposable] as [dispose] method is expected + * to be called directly. + */ +interface PythonConsoleBackendServiceDisposable : PythonConsoleBackendService.Iface { + fun dispose() +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/console/PythonConsoleClientUtil.kt b/python/src/com/jetbrains/python/console/PythonConsoleClientUtil.kt index 1b416eb4e77c..19832a79b969 100644 --- a/python/src/com/jetbrains/python/console/PythonConsoleClientUtil.kt +++ b/python/src/com/jetbrains/python/console/PythonConsoleClientUtil.kt @@ -8,60 +8,57 @@ import java.lang.reflect.InvocationHandler import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method import java.lang.reflect.Proxy -import java.util.concurrent.* +import java.util.concurrent.Callable +import java.util.concurrent.ExecutionException +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException private const val PYTHON_CONSOLE_COMMAND_THREAD_FACTORY_NAME: String = "Python Console Command Executor" -@JvmOverloads fun synchronizedPythonConsoleClient(loader: ClassLoader, delegate: PythonConsoleBackendService.Iface, - pythonConsoleProcess: Process? = null): PythonConsoleBackendService.Iface { - val executorService = newSingleThreadPythonConsoleCommandExecutor() - return Proxy.newProxyInstance(loader, arrayOf>(PythonConsoleBackendService.Iface::class.java), - InvocationHandler { _, method, args -> - // we evaluate the original method in the other thread in order to control it - val future = executorService.submit(Callable { - return@Callable invokeOriginalMethod(args, method, delegate) - }) + pythonConsoleProcess: Process): PythonConsoleBackendServiceDisposable { + val executorService = ConcurrencyUtil.newSingleThreadExecutor(PYTHON_CONSOLE_COMMAND_THREAD_FACTORY_NAME) + // make the `PythonConsoleBackendService.Iface` process-aware and thread-safe + val proxy = Proxy.newProxyInstance(loader, arrayOf>(PythonConsoleBackendService.Iface::class.java), + InvocationHandler { _, method, args -> + // we evaluate the original method in the other thread in order to control it + val future = executorService.submit(Callable { + return@Callable invokeOriginalMethod(args, method, delegate) + }) - if (pythonConsoleProcess == null) { - try { - return@InvocationHandler future.get() - } - catch (e: ExecutionException) { - throw e.cause ?: e - } - } - - while (true) { - try { - return@InvocationHandler future.get(10L, TimeUnit.MILLISECONDS) - } - catch (e: TimeoutException) { - if (!pythonConsoleProcess.isAlive) { - val exitValue = pythonConsoleProcess.exitValue() - throw PyConsoleProcessFinishedException(exitValue) - } - // continue waiting for the end of the operation execution - } - catch (e: ExecutionException) { - if (!pythonConsoleProcess.isAlive) { - val exitValue = pythonConsoleProcess.exitValue() - throw PyConsoleProcessFinishedException(exitValue) - } - throw e.cause ?: e - } - } - }) as PythonConsoleBackendService.Iface -} - -/** - * Creates new single thread executor with [ThreadPoolExecutor.keepAliveTime] - * equals to 60 sec. - */ -private fun newSingleThreadPythonConsoleCommandExecutor(): ExecutorService { - val threadFactory = ConcurrencyUtil.newNamedThreadFactory(PYTHON_CONSOLE_COMMAND_THREAD_FACTORY_NAME) - return ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, LinkedBlockingQueue(), threadFactory) + while (true) { + try { + return@InvocationHandler future.get(10L, TimeUnit.MILLISECONDS) + } + catch (e: TimeoutException) { + if (!pythonConsoleProcess.isAlive) { + val exitValue = pythonConsoleProcess.exitValue() + throw PyConsoleProcessFinishedException(exitValue) + } + // continue waiting for the end of the operation execution + } + catch (e: ExecutionException) { + if (!pythonConsoleProcess.isAlive) { + val exitValue = pythonConsoleProcess.exitValue() + throw PyConsoleProcessFinishedException(exitValue) + } + throw e.cause ?: e + } + } + }) as PythonConsoleBackendService.Iface + // make the `proxy` disposable + return object : PythonConsoleBackendServiceDisposable, PythonConsoleBackendService.Iface by proxy { + override fun dispose() { + executorService.shutdownNow() + try { + while (!executorService.awaitTermination(1L, TimeUnit.SECONDS)) Unit + } + catch (e: InterruptedException) { + Thread.currentThread().interrupt() + } + } + } } private fun invokeOriginalMethod(args: Array?, method: Method, delegate: Any): Any? {