Skip to content

Commit c8624cd

Browse files
gh-138860: Lazy import rlcompleter in pdb to avoid deadlock in subprocess (#139185)
1 parent 9e64938 commit c8624cd

File tree

3 files changed

+34
-4
lines changed

3 files changed

+34
-4
lines changed

Lib/pdb.py

Lines changed: 11 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,15 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
364363
readline.set_completer_delims(' \t\n`@#%^&*()=+[{]}\\|;:\'",<>?')
365364
except ImportError:
366365
pass
366+
367+
# GH-138860
368+
# We need to lazy-import rlcompleter to avoid deadlock
369+
# We cannot import it during self.complete* methods because importing
370+
# rlcompleter for the first time will overwrite readline's completer
371+
# So we import it here and save the Completer class
372+
from rlcompleter import Completer
373+
self.RlCompleter = Completer
374+
367375
self.allow_kbdint = False
368376
self.nosigint = nosigint
369377
# Consider these characters as part of the command so when the users type
@@ -1186,10 +1194,9 @@ def completedefault(self, text, line, begidx, endidx):
11861194
conv_vars = self.curframe.f_globals.get('__pdb_convenience_variables', {})
11871195
return [f"${name}" for name in conv_vars if name.startswith(text[1:])]
11881196

1189-
# Use rlcompleter to do the completion
11901197
state = 0
11911198
matches = []
1192-
completer = Completer(self.curframe.f_globals | self.curframe.f_locals)
1199+
completer = self.RlCompleter(self.curframe.f_globals | self.curframe.f_locals)
11931200
while (match := completer.complete(text, state)) is not None:
11941201
matches.append(match)
11951202
state += 1
@@ -1204,8 +1211,8 @@ def _enable_rlcompleter(self, ns):
12041211
return
12051212

12061213
try:
1214+
completer = self.RlCompleter(ns)
12071215
old_completer = readline.get_completer()
1208-
completer = Completer(ns)
12091216
readline.set_completer(completer.complete)
12101217
yield
12111218
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):
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.

0 commit comments

Comments
 (0)