From 64afd00619081d0b66b0e820409d2e6455bb8cce Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sat, 20 Sep 2025 19:48:02 +0200 Subject: [PATCH 1/5] Lazy import rlcompleter in pdb to avoid deadlock in subprocess --- Lib/pdb.py | 7 ++++++- Lib/test/test_pdb.py | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index a783583a2b1c38..67e98415b5e576 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -100,7 +100,6 @@ import _pyrepl.utils from contextlib import ExitStack, closing, contextmanager -from rlcompleter import Completer from types import CodeType from warnings import deprecated @@ -1187,6 +1186,9 @@ def completedefault(self, text, line, begidx, endidx): return [f"${name}" for name in conv_vars if name.startswith(text[1:])] # Use rlcompleter to do the completion + # GH-138860 + # This needs to be lazy-imported to avoid deadlock + from rlcompleter import Completer state = 0 matches = [] completer = Completer(self.curframe.f_globals | self.curframe.f_locals) @@ -1205,6 +1207,9 @@ def _enable_rlcompleter(self, ns): try: old_completer = readline.get_completer() + # GH-138860 + # This needs to be lazy-imported to avoid deadlock + from rlcompleter import Completer completer = Completer(ns) readline.set_completer(completer.complete) yield diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 6b74e21ad73d1a..c4e0910a2cbf4d 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -4688,6 +4688,28 @@ def foo(): stdout, _ = self._run_script(script, commands) self.assertIn("42", stdout) + def test_readline_not_imported(self): + """GH-138860 + Directly or indirectly importing readline might deadlock a subprocess + if it's launched with process_group=0 or preexec_fn=setpgrp + + It's also a pattern that readline is never imported with just import pdb. + + This test is to ensure that readline is not imported for import pdb. + It's possible that we have a good reason to do that in the future. + """ + + script = textwrap.dedent(""" + import sys + import pdb + if "readline" in sys.modules: + print("readline imported") + """) + commands = "" + stdout, stderr = self._run_script(script, commands) + self.assertNotIn("readline imported", stdout) + self.assertEqual(stderr, "") + @support.force_colorized_test_class class PdbTestColorize(unittest.TestCase): From 5856bda96eef449a29897660cef7b4d2574df6bc Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:50:36 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst diff --git a/Misc/NEWS.d/next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst b/Misc/NEWS.d/next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst new file mode 100644 index 00000000000000..0903eb71ae4346 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-20-17-50-31.gh-issue-138860.Y9JXap.rst @@ -0,0 +1 @@ +Lazy import :mod:`rlcompleter` in :mod:`pdb` to avoid deadlock in subprocess. From f4673824511a87e1381cf300e16f83732f6734a9 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sat, 20 Sep 2025 22:01:25 +0200 Subject: [PATCH 3/5] Remove blank spaces --- Lib/test/test_pdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index c4e0910a2cbf4d..9a7d855003551a 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -4692,7 +4692,7 @@ def test_readline_not_imported(self): """GH-138860 Directly or indirectly importing readline might deadlock a subprocess if it's launched with process_group=0 or preexec_fn=setpgrp - + It's also a pattern that readline is never imported with just import pdb. This test is to ensure that readline is not imported for import pdb. From 42c3e9c143141ca240f324042aec3bf168a42e2a Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sun, 21 Sep 2025 20:07:35 +0200 Subject: [PATCH 4/5] Do not import rlcompleter in complete* methods --- Lib/pdb.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 67e98415b5e576..cf2fcd887e0495 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -363,6 +363,15 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, readline.set_completer_delims(' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?') except ImportError: pass + + # GH-138860 + # We need to lazy-import rlcompleter to avoid deadlock + # We cannot import it during self.complete* methods because importing + # rlcompleter for the first time will overwrite readline's completer + # So we import it here and save the Completer class + from rlcompleter import Completer + self.RlCompleter = Completer + self.allow_kbdint = False self.nosigint = nosigint # Consider these characters as part of the command so when the users type @@ -1185,13 +1194,9 @@ def completedefault(self, text, line, begidx, endidx): conv_vars = self.curframe.f_globals.get('__pdb_convenience_variables', {}) return [f"${name}" for name in conv_vars if name.startswith(text[1:])] - # Use rlcompleter to do the completion - # GH-138860 - # This needs to be lazy-imported to avoid deadlock - from rlcompleter import Completer state = 0 matches = [] - completer = Completer(self.curframe.f_globals | self.curframe.f_locals) + completer = self.RlCompleter(self.curframe.f_globals | self.curframe.f_locals) while (match := completer.complete(text, state)) is not None: matches.append(match) state += 1 @@ -1206,11 +1211,8 @@ def _enable_rlcompleter(self, ns): return try: + completer = self.RlCompleter(ns) old_completer = readline.get_completer() - # GH-138860 - # This needs to be lazy-imported to avoid deadlock - from rlcompleter import Completer - completer = Completer(ns) readline.set_completer(completer.complete) yield finally: @@ -3155,6 +3157,7 @@ def read_command(self, prompt): def readline_completion(self, completer): try: import readline + import rlcompleter except ImportError: yield return From 9364e1c732d76eeb95268a6da4460410fae9cc4c Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Sun, 21 Sep 2025 20:44:28 +0200 Subject: [PATCH 5/5] Remove leftover --- Lib/pdb.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index cf2fcd887e0495..fd48882e28fe7c 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -3157,7 +3157,6 @@ def read_command(self, prompt): def readline_completion(self, completer): try: import readline - import rlcompleter except ImportError: yield return