@@ -17,7 +17,10 @@ def user_line(self, frame):
1717 self .set_step ()
1818 return
1919 message = self .__frame2message (frame )
20- self .gui .interaction (message , frame )
20+ try :
21+ self .gui .interaction (message , frame )
22+ except (TclError , RuntimeError ):
23+ pass
2124
2225 def user_exception (self , frame , info ):
2326 if self .in_rpc_code (frame ):
@@ -59,8 +62,42 @@ def __init__(self, pyshell, idb=None):
5962 self .frame = None
6063 self .make_gui ()
6164 self .interacting = 0
65+ self .nesting_level = 0
6266
6367 def run (self , * args ):
68+ # Deal with the scenario where we've already got a program running
69+ # in the debugger and we want to start another. If that is the case,
70+ # our second 'run' was invoked from an event dispatched not from
71+ # the main event loop, but from the nested event loop in 'interaction'
72+ # below. So our stack looks something like this:
73+ # outer main event loop
74+ # run()
75+ # <running program with traces>
76+ # callback to debugger's interaction()
77+ # nested event loop
78+ # run() for second command
79+ #
80+ # This kind of nesting of event loops causes all kinds of problems
81+ # (see e.g. issue #24455) especially when dealing with running as a
82+ # subprocess, where there's all kinds of extra stuff happening in
83+ # there - insert a traceback.print_stack() to check it out.
84+ #
85+ # By this point, we've already called restart_subprocess() in
86+ # ScriptBinding. However, we also need to unwind the stack back to
87+ # that outer event loop. To accomplish this, we:
88+ # - return immediately from the nested run()
89+ # - abort_loop ensures the nested event loop will terminate
90+ # - the debugger's interaction routine completes normally
91+ # - the restart_subprocess() will have taken care of stopping
92+ # the running program, which will also let the outer run complete
93+ #
94+ # That leaves us back at the outer main event loop, at which point our
95+ # after event can fire, and we'll come back to this routine with a
96+ # clean stack.
97+ if self .nesting_level > 0 :
98+ self .abort_loop ()
99+ self .root .after (100 , lambda : self .run (* args ))
100+ return
64101 try :
65102 self .interacting = 1
66103 return self .idb .run (* args )
@@ -71,6 +108,7 @@ def close(self, event=None):
71108 if self .interacting :
72109 self .top .bell ()
73110 return
111+ self .abort_loop ()
74112 if self .stackviewer :
75113 self .stackviewer .close (); self .stackviewer = None
76114 # Clean up pyshell if user clicked debugger control close widget.
@@ -191,7 +229,12 @@ def interaction(self, message, frame, info=None):
191229 b .configure (state = "normal" )
192230 #
193231 self .top .wakeup ()
194- self .root .mainloop ()
232+ # Nested main loop: Tkinter's main loop is not reentrant, so use
233+ # Tcl's vwait facility, which reenters the event loop until an
234+ # event handler sets the variable we're waiting on
235+ self .nesting_level += 1
236+ self .root .tk .call ('vwait' , '::idledebugwait' )
237+ self .nesting_level -= 1
195238 #
196239 for b in self .buttons :
197240 b .configure (state = "disabled" )
@@ -215,23 +258,26 @@ def __frame2fileline(self, frame):
215258
216259 def cont (self ):
217260 self .idb .set_continue ()
218- self .root . quit ()
261+ self .abort_loop ()
219262
220263 def step (self ):
221264 self .idb .set_step ()
222- self .root . quit ()
265+ self .abort_loop ()
223266
224267 def next (self ):
225268 self .idb .set_next (self .frame )
226- self .root . quit ()
269+ self .abort_loop ()
227270
228271 def ret (self ):
229272 self .idb .set_return (self .frame )
230- self .root . quit ()
273+ self .abort_loop ()
231274
232275 def quit (self ):
233276 self .idb .set_quit ()
234- self .root .quit ()
277+ self .abort_loop ()
278+
279+ def abort_loop (self ):
280+ self .root .tk .call ('set' , '::idledebugwait' , '1' )
235281
236282 stackviewer = None
237283
0 commit comments