diff --git a/_pydevd_bundle/pydevconsole_code.py b/_pydevd_bundle/pydevconsole_code.py index 760036e0..3f8fa6ce 100644 --- a/_pydevd_bundle/pydevconsole_code.py +++ b/_pydevd_bundle/pydevconsole_code.py @@ -212,33 +212,8 @@ def __call__(self, source, filename="", symbol="single"): __all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact", "compile_command"] -from _pydev_bundle._pydev_saved_modules import threading - -class _EvalAwaitInNewEventLoop(threading.Thread): - def __init__(self, compiled, updated_globals, updated_locals): - threading.Thread.__init__(self) - self.daemon = True - self._compiled = compiled - self._updated_globals = updated_globals - self._updated_locals = updated_locals - - # Output - self.evaluated_value = None - self.exc = None - - async def _async_func(self): - return await eval(self._compiled, self._updated_locals, self._updated_globals) - - def run(self): - try: - import asyncio - - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - self.evaluated_value = asyncio.run(self._async_func()) - except: - self.exc = sys.exc_info() +from _pydevd_bundle.pydevd_async_utils import eval_async_coro class InteractiveInterpreter: @@ -321,13 +296,7 @@ def runcode(self, code): is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE if is_async: - t = _EvalAwaitInNewEventLoop(code, self.locals, None) - t.start() - t.join() - - if t.exc: - raise t.exc[1].with_traceback(t.exc[2]) - + eval_async_coro(code, self.locals, None) else: exec(code, self.locals) except SystemExit: diff --git a/_pydevd_bundle/pydevd_async_utils.py b/_pydevd_bundle/pydevd_async_utils.py new file mode 100644 index 00000000..396db111 --- /dev/null +++ b/_pydevd_bundle/pydevd_async_utils.py @@ -0,0 +1,45 @@ +__all__ = ["eval_async_coro"] + +import types + + +def _get_current_loop(): + import asyncio + + try: + return asyncio.get_running_loop() + except (RuntimeError, AttributeError): + return asyncio.new_event_loop() + + +def _prepare_coro(coro, _locals, _globals): + if isinstance(coro, types.CodeType): + return eval(coro, _locals, _globals) + + return coro + + +def eval_async_coro(coro, _locals, _globals): + import asyncio + + coro = _prepare_coro(coro, _locals, _globals) + loop = _get_current_loop() + + if not loop.is_running(): + return loop.run_until_complete(coro) + + current = asyncio.current_task(loop) + + t = loop.create_task(coro) + + try: + if current is not None: + asyncio._leave_task(loop, current) + + while not t.done(): + loop._run_once() + + return t.result() + finally: + if current is not None: + asyncio._enter_task(loop, current) diff --git a/_pydevd_bundle/pydevd_console.py b/_pydevd_bundle/pydevd_console.py index 3be04b79..0bf1d524 100644 --- a/_pydevd_bundle/pydevd_console.py +++ b/_pydevd_bundle/pydevd_console.py @@ -2,12 +2,13 @@ import sys import traceback -from _pydevd_bundle.pydevconsole_code import InteractiveConsole, _EvalAwaitInNewEventLoop +from _pydevd_bundle.pydevconsole_code import InteractiveConsole from _pydev_bundle import _pydev_completer from _pydev_bundle.pydev_console_utils import BaseInterpreterInterface, BaseStdIn from _pydev_bundle.pydev_imports import Exec from _pydev_bundle.pydev_override import overrides from _pydevd_bundle import pydevd_save_locals +from _pydevd_bundle.pydevd_async_utils import eval_async_coro from _pydevd_bundle.pydevd_io import IOBuf from pydevd_tracing import get_exception_traceback_str from _pydevd_bundle.pydevd_xml import make_valid_xml_value @@ -160,14 +161,10 @@ def runcode(self, code): is_async = inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE if is_async: - t = _EvalAwaitInNewEventLoop(code, updated_globals, updated_locals) - t.start() - t.join() - - update_globals_and_locals(updated_globals, initial_globals, self.frame) - if t.exc: - raise t.exc[1].with_traceback(t.exc[2]) - + try: + eval_async_coro(code, updated_globals, updated_locals) + finally: + update_globals_and_locals(updated_globals, initial_globals, self.frame) else: try: exec(code, updated_globals, updated_locals) diff --git a/_pydevd_bundle/pydevd_vars.py b/_pydevd_bundle/pydevd_vars.py index 0f3d6250..132536eb 100644 --- a/_pydevd_bundle/pydevd_vars.py +++ b/_pydevd_bundle/pydevd_vars.py @@ -3,6 +3,8 @@ """ import pickle + +from _pydevd_bundle.pydevd_async_utils import eval_async_coro from _pydevd_bundle.pydevd_constants import get_frame, get_current_thread_id, iter_chars, silence_warnings_decorator, get_global_debugger from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate, get_type, var_to_xml @@ -253,18 +255,7 @@ def eval_in_context(expression, global_vars, local_vars, py_db=None): is_async = inspect.CO_COROUTINE & compiled.co_flags == inspect.CO_COROUTINE if is_async: - if py_db is None: - py_db = get_global_debugger() - if py_db is None: - raise RuntimeError("Cannot evaluate async without py_db.") - t = _EvalAwaitInNewEventLoop(py_db, compiled, global_vars, local_vars) - t.start() - t.join() - - if t.exc: - raise t.exc[1].with_traceback(t.exc[2]) - else: - result = t.evaluated_value + result = eval_async_coro(compiled, local_vars, global_vars) else: result = eval(compiled, global_vars, local_vars) except (Exception, KeyboardInterrupt): @@ -416,31 +407,6 @@ def _compile_as_exec(expression): return compile(expression_to_evaluate, "", "exec") -class _EvalAwaitInNewEventLoop(PyDBDaemonThread): - def __init__(self, py_db, compiled, updated_globals, updated_locals): - PyDBDaemonThread.__init__(self, py_db) - self._compiled = compiled - self._updated_globals = updated_globals - self._updated_locals = updated_locals - - # Output - self.evaluated_value = None - self.exc = None - - async def _async_func(self): - return await eval(self._compiled, self._updated_locals, self._updated_globals) - - def _on_run(self): - try: - import asyncio - - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - self.evaluated_value = asyncio.run(self._async_func()) - except: - self.exc = sys.exc_info() - - @_evaluate_with_timeouts def evaluate_expression(py_db, frame, expression, is_exec): """ @@ -550,12 +516,7 @@ def method(): compiled = _compile_as_exec(expression) is_async = inspect.CO_COROUTINE & compiled.co_flags == inspect.CO_COROUTINE if is_async: - t = _EvalAwaitInNewEventLoop(py_db, compiled, updated_globals, updated_locals) - t.start() - t.join() - - if t.exc: - raise t.exc[1].with_traceback(t.exc[2]) + eval_async_coro(compiled, updated_globals, updated_locals) else: Exec(compiled, updated_globals, updated_locals) finally: @@ -564,14 +525,7 @@ def method(): else: is_async = inspect.CO_COROUTINE & compiled.co_flags == inspect.CO_COROUTINE if is_async: - t = _EvalAwaitInNewEventLoop(py_db, compiled, updated_globals, updated_locals) - t.start() - t.join() - - if t.exc: - raise t.exc[1].with_traceback(t.exc[2]) - else: - result = t.evaluated_value + result = eval_async_coro(compiled, updated_globals, updated_locals) else: result = eval(compiled, updated_globals, updated_locals) if result is not None: # Only print if it's not None (as python does)