Skip to content

Commit 51b1043

Browse files
[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 c8624cd) * gh-139289: Lazy import rlcompleter to fix the refleak (GH-139305) (cherry picked from commit 8288f36) Co-authored-by: Tian Gao <[email protected]>
1 parent ade6dea commit 51b1043

File tree

5 files changed

+53
-5
lines changed

5 files changed

+53
-5
lines changed

Lib/pdb.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@
100100
import _pyrepl.utils
101101

102102
from contextlib import ExitStack, closing, contextmanager
103-
from rlcompleter import Completer
104103
from types import CodeType
105104
from warnings import deprecated
106105

@@ -364,6 +363,7 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
364363
readline.set_completer_delims(' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?')
365364
except ImportError:
366365
pass
366+
367367
self.allow_kbdint = False
368368
self.nosigint = nosigint
369369
# Consider these characters as part of the command so when the users type
@@ -1092,6 +1092,31 @@ def set_convenience_variable(self, frame, name, value):
10921092
# Generic completion functions. Individual complete_foo methods can be
10931093
# assigned below to one of these functions.
10941094

1095+
@property
1096+
def rlcompleter(self):
1097+
"""Return the `Completer` class from `rlcompleter`, while avoiding the
1098+
side effects of changing the completer from `import rlcompleter`.
1099+
1100+
This is a compromise between GH-138860 and GH-139289. If GH-139289 is
1101+
fixed, then we don't need this and we can just `import rlcompleter` in
1102+
`Pdb.__init__`.
1103+
"""
1104+
if not hasattr(self, "_rlcompleter"):
1105+
try:
1106+
import readline
1107+
except ImportError:
1108+
# readline is not available, just get the Completer
1109+
from rlcompleter import Completer
1110+
self._rlcompleter = Completer
1111+
else:
1112+
# importing rlcompleter could have side effect of changing
1113+
# the current completer, we need to restore it
1114+
prev_completer = readline.get_completer()
1115+
from rlcompleter import Completer
1116+
self._rlcompleter = Completer
1117+
readline.set_completer(prev_completer)
1118+
return self._rlcompleter
1119+
10951120
def completenames(self, text, line, begidx, endidx):
10961121
# Overwrite completenames() of cmd so for the command completion,
10971122
# if no current command matches, check for expressions as well
@@ -1186,10 +1211,9 @@ def completedefault(self, text, line, begidx, endidx):
11861211
conv_vars = self.curframe.f_globals.get('__pdb_convenience_variables', {})
11871212
return [f"${name}" for name in conv_vars if name.startswith(text[1:])]
11881213

1189-
# Use rlcompleter to do the completion
11901214
state = 0
11911215
matches = []
1192-
completer = Completer(self.curframe.f_globals | self.curframe.f_locals)
1216+
completer = self.rlcompleter(self.curframe.f_globals | self.curframe.f_locals)
11931217
while (match := completer.complete(text, state)) is not None:
11941218
matches.append(match)
11951219
state += 1
@@ -1204,8 +1228,8 @@ def _enable_rlcompleter(self, ns):
12041228
return
12051229

12061230
try:
1231+
completer = self.rlcompleter(ns)
12071232
old_completer = readline.get_completer()
1208-
completer = Completer(ns)
12091233
readline.set_completer(completer.complete)
12101234
yield
12111235
finally:

Lib/test/test_pdb.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4688,6 +4688,28 @@ def foo():
46884688
stdout, _ = self._run_script(script, commands)
46894689
self.assertIn("42", stdout)
46904690

4691+
def test_readline_not_imported(self):
4692+
"""GH-138860
4693+
Directly or indirectly importing readline might deadlock a subprocess
4694+
if it's launched with process_group=0 or preexec_fn=setpgrp
4695+
4696+
It's also a pattern that readline is never imported with just import pdb.
4697+
4698+
This test is to ensure that readline is not imported for import pdb.
4699+
It's possible that we have a good reason to do that in the future.
4700+
"""
4701+
4702+
script = textwrap.dedent("""
4703+
import sys
4704+
import pdb
4705+
if "readline" in sys.modules:
4706+
print("readline imported")
4707+
""")
4708+
commands = ""
4709+
stdout, stderr = self._run_script(script, commands)
4710+
self.assertNotIn("readline imported", stdout)
4711+
self.assertEqual(stderr, "")
4712+
46914713

46924714
@support.force_colorized_test_class
46934715
class PdbTestColorize(unittest.TestCase):

Lib/test/test_pyclbr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ def test_others(self):
254254
'pdb',
255255
# pyclbr does not handle elegantly `typing` or properties
256256
ignore=('Union', '_ModuleTarget', '_ScriptTarget', '_ZipTarget', 'curframe_locals',
257-
'_InteractState'),
257+
'_InteractState', 'rlcompleter'),
258258
)
259259
cm('pydoc', ignore=('input', 'output',)) # properties
260260

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lazy import :mod:`rlcompleter` in :mod:`pdb` to avoid deadlock in subprocess.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Do a real lazy-import on :mod:`rlcompleter` in :mod:`pdb` and restore the existing completer after importing :mod:`rlcompleter`.

0 commit comments

Comments
 (0)