##// END OF EJS Templates
Backport PR #13311: Use ThreadPoolExecutor for debugger cmdloop, solving infinite thread-creation and traceback loop
Matthias Bussonnier -
Show More
@@ -1,159 +1,146 b''
1 import asyncio
1 import asyncio
2 import signal
2 import signal
3 import sys
3 import sys
4 import threading
5
4
6 from IPython.core.debugger import Pdb
5 from IPython.core.debugger import Pdb
7
8 from IPython.core.completer import IPCompleter
6 from IPython.core.completer import IPCompleter
9 from .ptutils import IPythonPTCompleter
7 from .ptutils import IPythonPTCompleter
10 from .shortcuts import create_ipython_shortcuts, suspend_to_bg, cursor_in_leading_ws
8 from .shortcuts import create_ipython_shortcuts, suspend_to_bg, cursor_in_leading_ws
11
9
12 from prompt_toolkit.enums import DEFAULT_BUFFER
10 from prompt_toolkit.enums import DEFAULT_BUFFER
13 from prompt_toolkit.filters import (Condition, has_focus, has_selection,
11 from prompt_toolkit.filters import (Condition, has_focus, has_selection,
14 vi_insert_mode, emacs_insert_mode)
12 vi_insert_mode, emacs_insert_mode)
15 from prompt_toolkit.key_binding import KeyBindings
13 from prompt_toolkit.key_binding import KeyBindings
16 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
14 from prompt_toolkit.key_binding.bindings.completion import display_completions_like_readline
17 from pygments.token import Token
15 from pygments.token import Token
18 from prompt_toolkit.shortcuts.prompt import PromptSession
16 from prompt_toolkit.shortcuts.prompt import PromptSession
19 from prompt_toolkit.enums import EditingMode
17 from prompt_toolkit.enums import EditingMode
20 from prompt_toolkit.formatted_text import PygmentsTokens
18 from prompt_toolkit.formatted_text import PygmentsTokens
19 from concurrent.futures import ThreadPoolExecutor
21
20
22 from prompt_toolkit import __version__ as ptk_version
21 from prompt_toolkit import __version__ as ptk_version
23 PTK3 = ptk_version.startswith('3.')
22 PTK3 = ptk_version.startswith('3.')
24
23
25
24
26 class TerminalPdb(Pdb):
25 class TerminalPdb(Pdb):
27 """Standalone IPython debugger."""
26 """Standalone IPython debugger."""
28
27
29 def __init__(self, *args, pt_session_options=None, **kwargs):
28 def __init__(self, *args, pt_session_options=None, **kwargs):
30 Pdb.__init__(self, *args, **kwargs)
29 Pdb.__init__(self, *args, **kwargs)
31 self._ptcomp = None
30 self._ptcomp = None
32 self.pt_init(pt_session_options)
31 self.pt_init(pt_session_options)
32 self.thread_executor = ThreadPoolExecutor(1)
33
33
34 def pt_init(self, pt_session_options=None):
34 def pt_init(self, pt_session_options=None):
35 """Initialize the prompt session and the prompt loop
35 """Initialize the prompt session and the prompt loop
36 and store them in self.pt_app and self.pt_loop.
36 and store them in self.pt_app and self.pt_loop.
37
37
38 Additional keyword arguments for the PromptSession class
38 Additional keyword arguments for the PromptSession class
39 can be specified in pt_session_options.
39 can be specified in pt_session_options.
40 """
40 """
41 if pt_session_options is None:
41 if pt_session_options is None:
42 pt_session_options = {}
42 pt_session_options = {}
43
43
44 def get_prompt_tokens():
44 def get_prompt_tokens():
45 return [(Token.Prompt, self.prompt)]
45 return [(Token.Prompt, self.prompt)]
46
46
47 if self._ptcomp is None:
47 if self._ptcomp is None:
48 compl = IPCompleter(shell=self.shell,
48 compl = IPCompleter(shell=self.shell,
49 namespace={},
49 namespace={},
50 global_namespace={},
50 global_namespace={},
51 parent=self.shell,
51 parent=self.shell,
52 )
52 )
53 # add a completer for all the do_ methods
53 # add a completer for all the do_ methods
54 methods_names = [m[3:] for m in dir(self) if m.startswith("do_")]
54 methods_names = [m[3:] for m in dir(self) if m.startswith("do_")]
55
55
56 def gen_comp(self, text):
56 def gen_comp(self, text):
57 return [m for m in methods_names if m.startswith(text)]
57 return [m for m in methods_names if m.startswith(text)]
58 import types
58 import types
59 newcomp = types.MethodType(gen_comp, compl)
59 newcomp = types.MethodType(gen_comp, compl)
60 compl.custom_matchers.insert(0, newcomp)
60 compl.custom_matchers.insert(0, newcomp)
61 # end add completer.
61 # end add completer.
62
62
63 self._ptcomp = IPythonPTCompleter(compl)
63 self._ptcomp = IPythonPTCompleter(compl)
64
64
65 options = dict(
65 options = dict(
66 message=(lambda: PygmentsTokens(get_prompt_tokens())),
66 message=(lambda: PygmentsTokens(get_prompt_tokens())),
67 editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
67 editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
68 key_bindings=create_ipython_shortcuts(self.shell),
68 key_bindings=create_ipython_shortcuts(self.shell),
69 history=self.shell.debugger_history,
69 history=self.shell.debugger_history,
70 completer=self._ptcomp,
70 completer=self._ptcomp,
71 enable_history_search=True,
71 enable_history_search=True,
72 mouse_support=self.shell.mouse_support,
72 mouse_support=self.shell.mouse_support,
73 complete_style=self.shell.pt_complete_style,
73 complete_style=self.shell.pt_complete_style,
74 style=getattr(self.shell, "style", None),
74 style=getattr(self.shell, "style", None),
75 color_depth=self.shell.color_depth,
75 color_depth=self.shell.color_depth,
76 )
76 )
77
77
78 if not PTK3:
78 if not PTK3:
79 options['inputhook'] = self.shell.inputhook
79 options['inputhook'] = self.shell.inputhook
80 options.update(pt_session_options)
80 options.update(pt_session_options)
81 self.pt_loop = asyncio.new_event_loop()
81 self.pt_loop = asyncio.new_event_loop()
82 self.pt_app = PromptSession(**options)
82 self.pt_app = PromptSession(**options)
83
83
84 def cmdloop(self, intro=None):
84 def cmdloop(self, intro=None):
85 """Repeatedly issue a prompt, accept input, parse an initial prefix
85 """Repeatedly issue a prompt, accept input, parse an initial prefix
86 off the received input, and dispatch to action methods, passing them
86 off the received input, and dispatch to action methods, passing them
87 the remainder of the line as argument.
87 the remainder of the line as argument.
88
88
89 override the same methods from cmd.Cmd to provide prompt toolkit replacement.
89 override the same methods from cmd.Cmd to provide prompt toolkit replacement.
90 """
90 """
91 if not self.use_rawinput:
91 if not self.use_rawinput:
92 raise ValueError('Sorry ipdb does not support use_rawinput=False')
92 raise ValueError('Sorry ipdb does not support use_rawinput=False')
93
93
94 # In order to make sure that prompt, which uses asyncio doesn't
94 # In order to make sure that prompt, which uses asyncio doesn't
95 # interfere with applications in which it's used, we always run the
95 # interfere with applications in which it's used, we always run the
96 # prompt itself in a different thread (we can't start an event loop
96 # prompt itself in a different thread (we can't start an event loop
97 # within an event loop). This new thread won't have any event loop
97 # within an event loop). This new thread won't have any event loop
98 # running, and here we run our prompt-loop.
98 # running, and here we run our prompt-loop.
99 self.preloop()
99 self.preloop()
100
100
101 try:
101 try:
102 if intro is not None:
102 if intro is not None:
103 self.intro = intro
103 self.intro = intro
104 if self.intro:
104 if self.intro:
105 self.stdout.write(str(self.intro)+"\n")
105 print(self.intro, file=self.stdout)
106 stop = None
106 stop = None
107 while not stop:
107 while not stop:
108 if self.cmdqueue:
108 if self.cmdqueue:
109 line = self.cmdqueue.pop(0)
109 line = self.cmdqueue.pop(0)
110 else:
110 else:
111 self._ptcomp.ipy_completer.namespace = self.curframe_locals
111 self._ptcomp.ipy_completer.namespace = self.curframe_locals
112 self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals
112 self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals
113
113
114 # Run the prompt in a different thread.
114 # Run the prompt in a different thread.
115 line = ''
116 keyboard_interrupt = False
117
118 def in_thread():
119 nonlocal line, keyboard_interrupt
120 try:
115 try:
121 line = self.pt_app.prompt()
116 line = self.thread_executor.submit(self.pt_app.prompt).result()
122 except EOFError:
117 except EOFError:
123 line = 'EOF'
118 line = "EOF"
124 except KeyboardInterrupt:
125 keyboard_interrupt = True
126
127 th = threading.Thread(target=in_thread)
128 th.start()
129 th.join()
130
119
131 if keyboard_interrupt:
132 raise KeyboardInterrupt
133 line = self.precmd(line)
120 line = self.precmd(line)
134 stop = self.onecmd(line)
121 stop = self.onecmd(line)
135 stop = self.postcmd(stop, line)
122 stop = self.postcmd(stop, line)
136 self.postloop()
123 self.postloop()
137 except Exception:
124 except Exception:
138 raise
125 raise
139
126
140
127
141 def set_trace(frame=None):
128 def set_trace(frame=None):
142 """
129 """
143 Start debugging from `frame`.
130 Start debugging from `frame`.
144
131
145 If frame is not specified, debugging starts from caller's frame.
132 If frame is not specified, debugging starts from caller's frame.
146 """
133 """
147 TerminalPdb().set_trace(frame or sys._getframe().f_back)
134 TerminalPdb().set_trace(frame or sys._getframe().f_back)
148
135
149
136
150 if __name__ == '__main__':
137 if __name__ == '__main__':
151 import pdb
138 import pdb
152 # IPython.core.debugger.Pdb.trace_dispatch shall not catch
139 # IPython.core.debugger.Pdb.trace_dispatch shall not catch
153 # bdb.BdbQuit. When started through __main__ and an exception
140 # bdb.BdbQuit. When started through __main__ and an exception
154 # happened after hitting "c", this is needed in order to
141 # happened after hitting "c", this is needed in order to
155 # be able to quit the debugging session (see #9950).
142 # be able to quit the debugging session (see #9950).
156 old_trace_dispatch = pdb.Pdb.trace_dispatch
143 old_trace_dispatch = pdb.Pdb.trace_dispatch
157 pdb.Pdb = TerminalPdb
144 pdb.Pdb = TerminalPdb
158 pdb.Pdb.trace_dispatch = old_trace_dispatch
145 pdb.Pdb.trace_dispatch = old_trace_dispatch
159 pdb.main()
146 pdb.main()
General Comments 0
You need to be logged in to leave comments. Login now