mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-18029 Fix thread leakage in PythonConsoleTest
This commit is contained in:
@@ -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<Void> stopFuture = myServer.stop();
|
||||
myServer = null;
|
||||
stopFuture.get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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<Class<*>>(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<Class<*>>(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<Runnable>(), 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<out Any>?, method: Method, delegate: Any): Any? {
|
||||
|
||||
Reference in New Issue
Block a user