mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 14:04:26 +08:00
Try to recover from errors that are happening in subprocesses
This commit is contained in:
@@ -42,3 +42,4 @@ from jedi.api import Script, Interpreter, set_debug_function, \
|
|||||||
preload_module, names
|
preload_module, names
|
||||||
from jedi import settings
|
from jedi import settings
|
||||||
from jedi.api.environment import find_virtualenvs, find_python_environments
|
from jedi.api.environment import find_virtualenvs, find_python_environments
|
||||||
|
from jedi.api.exceptions import InternalError
|
||||||
|
|||||||
2
jedi/api/exceptions.py
Normal file
2
jedi/api/exceptions.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
class InternalError(Exception):
|
||||||
|
pass
|
||||||
@@ -19,6 +19,7 @@ from jedi.cache import memoize_method
|
|||||||
from jedi.evaluate.compiled.subprocess import functions
|
from jedi.evaluate.compiled.subprocess import functions
|
||||||
from jedi.evaluate.compiled.access import DirectObjectAccess, AccessPath, \
|
from jedi.evaluate.compiled.access import DirectObjectAccess, AccessPath, \
|
||||||
SignatureParam
|
SignatureParam
|
||||||
|
from jedi.api.exceptions import InternalError
|
||||||
|
|
||||||
_PICKLE_PROTOCOL = 2
|
_PICKLE_PROTOCOL = 2
|
||||||
|
|
||||||
@@ -141,25 +142,6 @@ class _Subprocess(object):
|
|||||||
# stderr=subprocess.PIPE
|
# 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):
|
class _CompiledSubprocess(_Subprocess):
|
||||||
def __init__(self, executable):
|
def __init__(self, executable):
|
||||||
@@ -170,6 +152,7 @@ class _CompiledSubprocess(_Subprocess):
|
|||||||
os.path.dirname(os.path.dirname(parso_path))
|
os.path.dirname(os.path.dirname(parso_path))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
self._executable = executable
|
||||||
self._evaluator_deletion_queue = queue.deque()
|
self._evaluator_deletion_queue = queue.deque()
|
||||||
|
|
||||||
def run(self, evaluator, function, args=(), kwargs={}):
|
def run(self, evaluator, function, args=(), kwargs={}):
|
||||||
@@ -188,6 +171,38 @@ class _CompiledSubprocess(_Subprocess):
|
|||||||
def get_sys_path(self):
|
def get_sys_path(self):
|
||||||
return self._send(None, functions.get_sys_path, (), {})
|
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):
|
def delete_evaluator(self, evaluator_id):
|
||||||
"""
|
"""
|
||||||
Currently we are not deleting evalutors instantly. They only get
|
Currently we are not deleting evalutors instantly. They only get
|
||||||
|
|||||||
@@ -66,6 +66,13 @@ def get_builtin_module_names(evaluator):
|
|||||||
return list(map(force_unicode, sys.builtin_module_names))
|
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):
|
def _get_init_path(directory_path):
|
||||||
"""
|
"""
|
||||||
The __init__ file can be searched in a directory. If found return it, else
|
The __init__ file can be searched in a directory. If found return it, else
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
import jedi
|
||||||
from jedi._compatibility import py_version
|
from jedi._compatibility import py_version
|
||||||
from jedi.api.environment import Environment, get_default_environment, \
|
from jedi.api.environment import Environment, get_default_environment, \
|
||||||
InvalidPythonEnvironment, find_python_environments
|
InvalidPythonEnvironment, find_python_environments
|
||||||
@@ -45,3 +46,12 @@ def test_load_module(evaluator):
|
|||||||
assert access_handle.get_api_type() == 'module'
|
assert access_handle.get_api_type() == 'module'
|
||||||
with pytest.raises(AttributeError):
|
with pytest.raises(AttributeError):
|
||||||
access_handle.py__mro__()
|
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'
|
||||||
|
|||||||
Reference in New Issue
Block a user