From 0a3be914c1f1cddfd2a727121314d85aa5939caa Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 24 Sep 2025 22:09:59 +0800 Subject: [PATCH 1/5] Lazy import rlcompleter to fix the refleak --- Lib/pdb.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index fd48882e28fe7c..7929e57ba42d58 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -364,14 +364,6 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None, 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 @@ -1100,6 +1092,31 @@ def set_convenience_variable(self, frame, name, value): # 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 all + the side effects 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 @@ -1196,7 +1213,7 @@ def completedefault(self, text, line, begidx, endidx): state = 0 matches = [] - completer = self.RlCompleter(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 @@ -1211,7 +1228,7 @@ def _enable_rlcompleter(self, ns): return try: - completer = self.RlCompleter(ns) + completer = self.rlcompleter(ns) old_completer = readline.get_completer() readline.set_completer(completer.complete) yield From 0edea6638057b154d8defbc7b6ac268bc7546241 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 24 Sep 2025 22:14:37 +0800 Subject: [PATCH 2/5] Polish the comments --- Lib/pdb.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 7929e57ba42d58..f695a39332e461 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -1094,8 +1094,8 @@ def set_convenience_variable(self, frame, name, value): @property def rlcompleter(self): - """Return the `Completer` class from `rlcompleter`, while avoiding all - the side effects from `import rlcompleter`. + """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 From 9ede27bfbb74f18c657719cd0fbebbf9ae17c405 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:17:38 +0000 Subject: [PATCH 3/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-24-14-17-34.gh-issue-139289.Vmk25k.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-09-24-14-17-34.gh-issue-139289.Vmk25k.rst 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 00000000000000..ade005e10876a3 --- /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`. From 9d6b9d4fb07977afb92084d4caab1e61014d348c Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 24 Sep 2025 22:19:54 +0800 Subject: [PATCH 4/5] Fix typo --- .../next/Library/2025-09-24-14-17-34.gh-issue-139289.Vmk25k.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index ade005e10876a3..04162619cf218b 100644 --- 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 @@ -1 +1 @@ -Do a real lazy-import on :mod:`rlcompleter` in mod:`pdb` and restore the existing completer after importing :mod:`rlcompleter`. +Do a real lazy-import on :mod:`rlcompleter` in :mod:`pdb` and restore the existing completer after importing :mod:`rlcompleter`. From 06f63eb95ba9fb8fd6478c77b79a2c25e2436f25 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Wed, 24 Sep 2025 22:34:37 +0800 Subject: [PATCH 5/5] Fix the annoying clbr test --- Lib/test/test_pyclbr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py index bce68e6cd7a57e..79ef178f3807f4 100644 --- a/Lib/test/test_pyclbr.py +++ b/Lib/test/test_pyclbr.py @@ -250,7 +250,7 @@ def test_others(self): '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