Try to recover from errors that are happening in subprocesses

This commit is contained in:
Dave Halter
2018-01-02 00:23:13 +01:00
parent d93b613fd9
commit 927aa2bd91
5 changed files with 54 additions and 19 deletions

View File

@@ -42,3 +42,4 @@ from jedi.api import Script, Interpreter, set_debug_function, \
preload_module, names
from jedi import settings
from jedi.api.environment import find_virtualenvs, find_python_environments
from jedi.api.exceptions import InternalError

2
jedi/api/exceptions.py Normal file
View File

@@ -0,0 +1,2 @@
class InternalError(Exception):
pass

View File

@@ -19,6 +19,7 @@ from jedi.cache import memoize_method
from jedi.evaluate.compiled.subprocess import functions
from jedi.evaluate.compiled.access import DirectObjectAccess, AccessPath, \
SignatureParam
from jedi.api.exceptions import InternalError
_PICKLE_PROTOCOL = 2
@@ -141,25 +142,6 @@ class _Subprocess(object):
# stderr=subprocess.PIPE
)
def _send(self, evaluator_id, function, args=(), kwargs={}):
if not is_py3:
# Python 2 compatibility
kwargs = {force_unicode(key): value for key, value in kwargs.items()}
data = evaluator_id, function, args, kwargs
pickle.dump(data, self._process.stdin, protocol=_PICKLE_PROTOCOL)
self._process.stdin.flush()
is_exception, result = _pickle_load(self._process.stdout)
if is_exception:
raise result
return result
def terminate(self):
self._process.terminate()
def kill(self):
self._process.kill()
class _CompiledSubprocess(_Subprocess):
def __init__(self, executable):
@@ -170,6 +152,7 @@ class _CompiledSubprocess(_Subprocess):
os.path.dirname(os.path.dirname(parso_path))
)
)
self._executable = executable
self._evaluator_deletion_queue = queue.deque()
def run(self, evaluator, function, args=(), kwargs={}):
@@ -188,6 +171,38 @@ class _CompiledSubprocess(_Subprocess):
def get_sys_path(self):
return self._send(None, functions.get_sys_path, (), {})
def kill(self):
try:
subprocess = _subprocesses[self._executable]
except KeyError:
# Fine it was already removed from the cache.
pass
else:
# In the `!=` case there is already a new subprocess in place
# and we don't need to do anything here anymore.
if subprocess == self:
del _subprocesses[self._executable]
self._process.kill()
def _send(self, evaluator_id, function, args=(), kwargs={}):
if not is_py3:
# Python 2 compatibility
kwargs = {force_unicode(key): value for key, value in kwargs.items()}
data = evaluator_id, function, args, kwargs
pickle.dump(data, self._process.stdin, protocol=_PICKLE_PROTOCOL)
self._process.stdin.flush()
try:
is_exception, result = _pickle_load(self._process.stdout)
except EOFError:
self.kill()
raise InternalError("The subprocess crashed.")
if is_exception:
raise result
return result
def delete_evaluator(self, evaluator_id):
"""
Currently we are not deleting evalutors instantly. They only get

View File

@@ -66,6 +66,13 @@ def get_builtin_module_names(evaluator):
return list(map(force_unicode, sys.builtin_module_names))
def _test_raise_error(evaluator, exception_type):
"""
Raise an error to simulate certain problems for unit tests.
"""
raise exception_type
def _get_init_path(directory_path):
"""
The __init__ file can be searched in a directory. If found return it, else

View File

@@ -1,5 +1,6 @@
import pytest
import jedi
from jedi._compatibility import py_version
from jedi.api.environment import Environment, get_default_environment, \
InvalidPythonEnvironment, find_python_environments
@@ -45,3 +46,12 @@ def test_load_module(evaluator):
assert access_handle.get_api_type() == 'module'
with pytest.raises(AttributeError):
access_handle.py__mro__()
def test_error_in_environment(evaluator, Script):
# Provoke an error to show how Jedi can recover from it.
with pytest.raises(jedi.InternalError):
evaluator.compiled_subprocess._test_raise_error(KeyboardInterrupt)
# Jedi should still work.
def_, = Script('str').goto_definitions()
assert def_.name == 'str'