From 9428c6f69e997c59a094538c7638d3e8ebb66284 Mon Sep 17 00:00:00 2001 From: Muspi Merol Date: Tue, 2 Jul 2024 19:23:01 +0800 Subject: [PATCH] Ensure `Console` is locked and prevent `formatted_traceback` being unexpectedly truncated (#4905) Co-authored-by: Gyeongjae Choi --- docs/project/changelog.md | 7 +++++++ src/py/pyodide/console.py | 21 ++++++++++++--------- src/tests/test_console.py | 24 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/docs/project/changelog.md b/docs/project/changelog.md index 25eb1dbf5..a6f09293f 100644 --- a/docs/project/changelog.md +++ b/docs/project/changelog.md @@ -50,6 +50,13 @@ myst: in `CodeRunner` and `Console`. {pr}`4897` +- {{ Fix }} Fixed a bug that caused `Console`'s `formatted_traceback` being truncated + unexpectedly when `filename` is specified. + {pr}`4905` + +- {{ Fix }} Locked `PyodideConsole.runcode` to block `loadPackagesFromImports`. + {pr}`4905` + ### Packages - Upgraded `scikit-learn` to 1.5 {pr}`4823` diff --git a/src/py/pyodide/console.py b/src/py/pyodide/console.py index 76f17d915..a1d23825a 100644 --- a/src/py/pyodide/console.py +++ b/src/py/pyodide/console.py @@ -405,18 +405,21 @@ class Console: res.set_result(fut.result()) res = None - ensure_future(self.runcode(source, code)).add_done_callback(done_cb) + ensure_future(self._runcode_with_lock(source, code)).add_done_callback(done_cb) return res + async def _runcode_with_lock(self, source: str, code: CodeRunner) -> Any: + async with self._lock: + return await self.runcode(source, code) + async def runcode(self, source: str, code: CodeRunner) -> Any: """Execute a code object and return the result.""" - async with self._lock: - with self.redirect_streams(): - try: - return await code.run_async(self.globals) - finally: - sys.stdout.flush() - sys.stderr.flush() + with self.redirect_streams(): + try: + return await code.run_async(self.globals) + finally: + sys.stdout.flush() + sys.stderr.flush() def formatsyntaxerror(self, e: Exception) -> str: """Format the syntax error that just occurred. @@ -435,7 +438,7 @@ class Console: kept_frames = 0 # Try to trim out stack frames inside our code for frame, _ in traceback.walk_tb(tb): - keep_frames = keep_frames or frame.f_code.co_filename == "" + keep_frames = keep_frames or frame.f_code.co_filename == self.filename keep_frames = keep_frames or frame.f_code.co_filename == "" if keep_frames: kept_frames += 1 diff --git a/src/tests/test_console.py b/src/tests/test_console.py index 7e8cf7493..3e0944887 100644 --- a/src/tests/test_console.py +++ b/src/tests/test_console.py @@ -288,6 +288,7 @@ def test_nonpersistent_redirection(safe_sys_redirections): asyncio.run(test()) +@pytest.mark.asyncio async def test_compile_optimize(): from pyodide.console import Console @@ -300,6 +301,29 @@ async def test_compile_optimize(): assert await console.push("f.__doc__") is None +@pytest.mark.asyncio +async def test_console_filename(): + from pyodide.console import Console + + for filename in ("", "", "other"): + future = Console(filename=filename).push("assert 0") + with pytest.raises(AssertionError): + await future + assert isinstance(future.formatted_error, str) + assert f'File "{filename}", line 1, in ' in future.formatted_error + + +@pytest.mark.skip_refcount_check +@run_in_pyodide +async def test_pyodide_console_runcode_locked(selenium): + from pyodide.console import PyodideConsole + + console = PyodideConsole() + + console.push("import micropip") + await console.push("micropip") + + @pytest.mark.skip_refcount_check @run_in_pyodide async def test_console_imports(selenium):