From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:23:52 +0000 (+0200) Subject: [3.14] gh-138860: Lazy import rlcompleter in pdb to avoid deadlock in subprocess... X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=51b104302988b9da8e83edf6c42a9de8ca5a22c2;p=thirdparty%2FPython%2Fcpython.git [3.14] gh-138860: Lazy import rlcompleter in pdb to avoid deadlock in subprocess (GH-139185) (GH-139305) (GH-139280) * gh-138860: Lazy import rlcompleter in pdb to avoid deadlock in subprocess (GH-139185) (cherry picked from commit c8624cd36746b17d8f991cde63705e9419e940de) * gh-139289: Lazy import rlcompleter to fix the refleak (GH-139305) (cherry picked from commit 8288f3693f50058ad9b9fe04e01f5dad902d8bad) Co-authored-by: Tian Gao --- diff --git a/Lib/pdb.py b/Lib/pdb.py index 2d7da2f82bea..14ed25ed1470 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -100,7 +100,6 @@ import _colorize import _pyrepl.utils from contextlib import ExitStack, closing, contextmanager -from rlcompleter import Completer from types import CodeType from warnings import deprecated @@ -364,6 +363,7 @@ class Pdb(bdb.Bdb, cmd.Cmd): readline.set_completer_delims(' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?') except ImportError: pass + self.allow_kbdint = False self.nosigint = nosigint # Consider these characters as part of the command so when the users type @@ -1092,6 +1092,31 @@ class Pdb(bdb.Bdb, cmd.Cmd): # Generic completion functions. Individual complete_foo methods can be # assigned below to one of these functions. + @property + def rlcompleter(self): + """Return the `Completer` class from `rlcompleter`, while avoiding the + side effects of changing the completer from `import rlcompleter`. + + This is a compromise between GH-138860 and GH-139289. If GH-139289 is + fixed, then we don't need this and we can just `import rlcompleter` in + `Pdb.__init__`. + """ + if not hasattr(self, "_rlcompleter"): + try: + import readline + except ImportError: + # readline is not available, just get the Completer + from rlcompleter import Completer + self._rlcompleter = Completer + else: + # importing rlcompleter could have side effect of changing + # the current completer, we need to restore it + prev_completer = readline.get_completer() + from rlcompleter import Completer + self._rlcompleter = Completer + readline.set_completer(prev_completer) + return self._rlcompleter + def completenames(self, text, line, begidx, endidx): # Overwrite completenames() of cmd so for the command completion, # if no current command matches, check for expressions as well @@ -1186,10 +1211,9 @@ class Pdb(bdb.Bdb, cmd.Cmd): 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 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 @@ -1204,8 +1228,8 @@ class Pdb(bdb.Bdb, cmd.Cmd): return try: + completer = self.rlcompleter(ns) old_completer = readline.get_completer() - completer = Completer(ns) readline.set_completer(completer.complete) yield finally: diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 6b74e21ad73d..9a7d85500355 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -4688,6 +4688,28 @@ class PdbTestInline(unittest.TestCase): 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): diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py index 3e7b2cd0dc99..3062f4eb9be5 100644 --- a/Lib/test/test_pyclbr.py +++ b/Lib/test/test_pyclbr.py @@ -254,7 +254,7 @@ class PyclbrTest(TestCase): 'pdb', # pyclbr does not handle elegantly `typing` or properties ignore=('Union', '_ModuleTarget', '_ScriptTarget', '_ZipTarget', 'curframe_locals', - '_InteractState'), + '_InteractState', 'rlcompleter'), ) cm('pydoc', ignore=('input', 'output',)) # properties 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 000000000000..0903eb71ae43 --- /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. diff --git a/Misc/NEWS.d/next/Library/2025-09-24-14-17-34.gh-issue-139289.Vmk25k.rst b/Misc/NEWS.d/next/Library/2025-09-24-14-17-34.gh-issue-139289.Vmk25k.rst new file mode 100644 index 000000000000..04162619cf21 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-24-14-17-34.gh-issue-139289.Vmk25k.rst @@ -0,0 +1 @@ +Do a real lazy-import on :mod:`rlcompleter` in :mod:`pdb` and restore the existing completer after importing :mod:`rlcompleter`.