classification
Title: better Ctrl-C support in pdb (program can be resumed) (issue216067)
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.2
process
Status: closed Resolution: accepted
Dependencies: Superseder:
Assigned To: gregory.p.smith Nosy List: draghuram, georg.brandl, gregory.p.smith, isandler, r.david.murray
Priority: normal Keywords: patch

Created on 2009-10-31 19:37 by isandler, last changed 2010-12-04 16:15 by georg.brandl. This issue is now closed.

Files
File name Uploaded Description Edit
sig.patch.v0 isandler, 2009-10-31 19:37
sig.patch.v1 isandler, 2010-02-22 02:19
sig.patch.v2 isandler, 2010-02-27 04:11
sig.patch.v3.diff isandler, 2010-03-06 19:20 review
test_pdb2.py isandler, 2010-03-22 05:56
pdb-keyboardinterrupt.diff georg.brandl, 2010-07-30 22:30 review
Messages (23)
msg94764 - (view) Author: Ilya Sandler (isandler) Date: 2009-10-31 19:37
Currently, pressing Ctrl-C in pdb will terminate the program and throw
the user into post-mortem debugging.

Other debuggers (e.g gdb and pydb) treat Ctrl-C differently: Ctrl-C only
stops the program and the user can resume it if needed. 

I believe current pdb behavior is user-unfriendly (as wanting to stop
and then resume the execution is a very common use case which is not
supported by pdb at all (I think)).


The attached patch changes pdb's Ctrl-C behavior to match
gdb's: Ctrl-C will stop the program and the user can resume the
execution later.
msg94782 - (view) Author: Raghuram Devarakonda (draghuram) (Python triager) Date: 2009-11-01 04:45
It is better for this functionality to be added in "Cmd" module as that
will benefit all users of that module. Please see bug #1294 which has a
patch for this purpose. It would be nice if you can test with that patch
and see if pdb works as you expected.
msg94811 - (view) Author: Ilya Sandler (isandler) Date: 2009-11-01 22:44
No,I don't think patch in the issue #1294 addresses the problem which
I'm trying to solve.

I tried applying patch#1294, and Ctrl-C will still throws your debugger
into postmortem mode and I don't think you can change that by overriding
do_KeyboardInterrupt(): when do_KbdInterrupt() is called you cannot
resume execution (or at least I don't know any way of doing it).

My patch handles the SIGINT directly which allows it to set tracing and
resume the program immediately (and keyboardinterrupt is never raised)

I'll also add comments to patch#1294
msg99661 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2010-02-21 14:07
http://codereview.appspot.com/216067/show
msg99702 - (view) Author: Ilya Sandler (isandler) Date: 2010-02-22 02:19
I fixed some of the style issues mentioned on appspot. (I was not sure about some of them and responded to them in appspot comments).

Also sigHandler became sighandler for consistency with the rest of pdb.py.  

The new version of the patch is attached.

However, it appears that I've been a bit over-optimistic about the lack of side-effects. In particular, the patch results in an very ugly error message when Ctrl-C is pressed while at pdb prompt..


*** AttributeError: AttributeError("'NoneType' object has no attribute 'f_globals'",)

Everything still seems to be working, but it's definitely ugly (and behaves differently on Windows and Linux).

I will try to summarize possible Ctrl-C scenarios in a separate post
msg99706 - (view) Author: Ilya Sandler (isandler) Date: 2010-02-22 03:00
Here is a list of Ctrl-C scenarios: ("current" below means the prepatch version of pdb).

1. program is running (last command was "c", "n", etc). Currently, Ctrl-C throws debugger into postmortem. Desired behavior: interrupt the program. This is the only scenario supported by the patch.

2. Program is stopped (debugger is interacting with the user). Currently, Ctrl-C will throw debugger into postmortem. Desired behaviour: either ignore Ctrl-C or abandon the current command (that's what gdb does).

3. Program is stopped and pdb runs one of the commands which catch exceptions (e.g "p"). Currently, Ctrl-C will abort the command and return pdb to the prompt. I think this behavior should be kept.

4. Program finished (debugger is in postmortem). Currently, Ctrl-C will quit the debugger. Desired behavior: either ignore Ctrl-C or abandon the current command.

Essentially, I think the best behavior is to have Ctrl-C to interrupt whatever pdb is doing and return to the fresh prompt.

I am assuming that behavior on Windows and Linux should be identical/nearly identical.

Does the above list make sense?

I would greatly appreciate any feedback/comments/guidance/etc.
msg99708 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2010-02-22 03:28
Your summary makes sense to me, at least.
msg100177 - (view) Author: Ilya Sandler (isandler) Date: 2010-02-27 04:11
Another iteration of the patch. Now sigint_handler will generate 
KeyboardInterrupts when pdb is in the commandloop

I think this guarantees consistent "Ctrl-C interrupts the current pdb action" behavior and the program is still resumable.

The "commands" command required a bit of special handling to unroll the changes if KbdInt happens.

The patch was tested on Linux and Vista.
msg100220 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2010-02-28 20:14
I put your sig.patch.v2 in http://codereview.appspot.com/216067/show and added some comments there.  (and attempted to have it CC the tracker so they show up here but its been a while since i've done that, not sure how to make that work)
msg100221 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2010-02-28 20:23
Reviewers: gregory.p.smith, Benjamin, ilya.sandler,

Message:
Also, can you take a look at how the pdb unittests work and see if you
can come up with a way to unittest the KeyboardInterrupt behavior?

Particular for the 4 scenarios you outlined in your prior comments in
the bug (those all make sense to me).

http://codereview.appspot.com/216067/diff/2001/2002
File Lib/pdb.py (right):

http://codereview.appspot.com/216067/diff/2001/2002#newcode63
Lib/pdb.py:63: def sigint_handler(self, signum, frame):
Please move this below the __init__ definition.  It makes classes odd to
read when __init__ isn't the first method defined when people are
looking for the constructor to see how to use it.

http://codereview.appspot.com/216067/diff/2001/2002#newcode64
Lib/pdb.py:64: if self.allow_kbdint:
Initialize self.allow_kdbint in __init__ so that a SIGINT coming in
before _cmdloop has run doesn't cause an AttributeError.

http://codereview.appspot.com/216067/diff/2001/2002#newcode215
Lib/pdb.py:215: # keyboard interrupts allow for an easy way to interrupt
"to cancel the current command"

http://codereview.appspot.com/216067/diff/2001/2002#newcode356
Lib/pdb.py:356: #it appears that that when pdb is reading input from a
pipe
Space after the # please.

Also, could you add a comment in here describing what the effect of this
code is?

It looks like you catch KeyboardInterrupt here and remove the particular
interrupted command from the list of commands to run at the current
breakpoint but I may be misreading things as I haven't spent much time
in pdb.py.

Please review this at http://codereview.appspot.com/216067/show

Affected files:
   M     Lib/pdb.py

Index: Lib/pdb.py
===================================================================
--- Lib/pdb.py	(revision 78520)
+++ Lib/pdb.py	(working copy)
@@ -13,8 +13,10 @@
  import re
  import pprint
  import traceback
+import signal

+
  class Restart(Exception):
      """Causes a debugger to be restarted for the debugged python  
program."""
      pass
@@ -58,6 +60,13 @@

  class Pdb(bdb.Bdb, cmd.Cmd):

+    def sigint_handler(self, signum, frame):
+        if self.allow_kbdint:
+            raise KeyboardInterrupt()
+        print >>self.stdout, "\nProgram interrupted. (Use 'cont' to  
resume)."
+        self.set_step()
+        self.set_trace(frame)
+
      def __init__(self, completekey='tab', stdin=None, stdout=None,  
skip=None):
          bdb.Bdb.__init__(self, skip=skip)
          cmd.Cmd.__init__(self, completekey, stdin, stdout)
@@ -72,6 +81,7 @@
              import readline
          except ImportError:
              pass
+        signal.signal(signal.SIGINT, self.sigint_handler)

          # Read $HOME/.pdbrc and ./.pdbrc
          self.rcLines = []
@@ -176,7 +186,7 @@
              if not self.commands_silent[currentbp]:
                  self.print_stack_entry(self.stack[self.curindex])
              if self.commands_doprompt[currentbp]:
-                self.cmdloop()
+                self._cmdloop()
              self.forget()
              return
          return 1
@@ -199,11 +209,22 @@
          self.interaction(frame, exc_traceback)

      # General interaction function
+    def _cmdloop(self):
+        while 1:
+            try:
+                # keyboard interrupts allow for an easy way to interrupt
+                # cancel current command
+                self.allow_kbdint = True
+                self.cmdloop()
+                self.allow_kbdint = False
+                break
+            except KeyboardInterrupt:
+                print >>self.stdout, '--KeyboardInterrupt--'

      def interaction(self, frame, traceback):
          self.setup(frame, traceback)
          self.print_stack_entry(self.stack[self.curindex])
-        self.cmdloop()
+        self._cmdloop()
          self.forget()

      def displayhook(self, obj):
@@ -329,9 +350,18 @@
          prompt_back = self.prompt
          self.prompt = '(com) '
          self.commands_defining = True
-        self.cmdloop()
-        self.commands_defining = False
-        self.prompt = prompt_back
+        try:
+            self.cmdloop()
+        except (KeyboardInterrupt, IOError):
+            #it appears that that when pdb is reading input from a pipe
+            # we may get IOErrors, rather than KeyboardInterrupt
+            self.commands.pop(bnum)   # remove this cmd list
+            self.commands_doprompt.pop(bnum)
+            self.commands_silent.pop(bnum)
+            raise KeyboardInterrupt()
+        finally:
+            self.commands_defining = False
+            self.prompt = prompt_back

      def do_break(self, arg, temporary = 0):
          # break [ ([filename:]lineno | function) [, "condition"] ]
msg100542 - (view) Author: Ilya Sandler (isandler) Date: 2010-03-06 19:20
Another version of the patch is attached ( I think, I fixed all the remaining style issues).

I'll answer the testing question in a separate post
msg100543 - (view) Author: Ilya Sandler (isandler) Date: 2010-03-06 19:27
new version of the patch is uploaded to bugs.python.org

http://codereview.appspot.com/216067/diff/2001/2002
File Lib/pdb.py (right):

http://codereview.appspot.com/216067/diff/2001/2002#newcode63
Lib/pdb.py:63: def sigint_handler(self, signum, frame):
On 2010/02/28 20:12:00, gregory.p.smith wrote:
> Please move this below the __init__ definition.  It makes classes odd
to read
> when __init__ isn't the first method defined when people are looking
for the
> constructor to see how to use it.

Done.

http://codereview.appspot.com/216067/diff/2001/2002#newcode64
Lib/pdb.py:64: if self.allow_kbdint:
On 2010/02/28 20:12:00, gregory.p.smith wrote:
> Initialize self.allow_kdbint in __init__ so that a SIGINT coming in
before
> _cmdloop has run doesn't cause an AttributeError.

Done.

http://codereview.appspot.com/216067/diff/2001/2002#newcode215
Lib/pdb.py:215: # keyboard interrupts allow for an easy way to interrupt
On 2010/02/28 20:12:00, gregory.p.smith wrote:
> "to cancel the current command"

I changed the wording a bit, should be ok now.

http://codereview.appspot.com/216067/diff/2001/2002#newcode356
Lib/pdb.py:356: #it appears that that when pdb is reading input from a
pipe
On 2010/02/28 20:12:00, gregory.p.smith wrote:
> Space after the # please.

this code
> is?

particular
> interrupted command from the list of commands to run at the current
breakpoint
> but I may be misreading things as I haven't spent much time in pdb.py.

Done. I've also added a comment to explain what's going on.

http://codereview.appspot.com/216067/show
msg100555 - (view) Author: Ilya Sandler (isandler) Date: 2010-03-07 00:10
> Also, can you take a look at how the pdb unittests work and see if you
can come up with a way to unittest the KeyboardInterrupt behavior?

Yes, this is doable. In fact, I already have such tests written (unix only though). The tests are assert based but it should not be difficult to convert them to unittest. Moreover, I have many more tests for pdb written in the same style. 

However, these tests are not easily (and possibly not at all) integratable with existing test_pdb.py module. (See below for the problems). So would it be acceptable to have these tests as a separate module (test_pdb2.py or some such)? (I might also need some help with integrating the module)

Background
----------

Here is the basic problem: testing/debugging a debugger is hard by itself (you need to deal with 2 programs at once: the one being debugged and the debugger which run intermittently), now throw in doctest and it becomes much harder (as everything you do now gets covered by another layer). And now we need to test signals and some of the tests will likely be platform specific which makes it even worse. 

In contrast, in my tests the snippets of code are written into a tmp file and then are fed into debugger, the debugger itself is run as a subprocess. So if a test fails, it can be easily rerun under debugger manually and you can  test for things like pdb exits and stalls.

Here is a snippet from my pdb tests (just to give an idea how they would look like: essentially, send a command to pdb, check the response).


  pdb=PdbTester("pdb_t_hello","""\
  import time
  for i in xrange(100000000):
     time.sleep(0.05)
  """)

  pdb.pdbHandle.stdin.write("c\n")
  time.sleep(0.01)
  #pdb_t_hello running, try to interrupt it
  pdb.pdbHandle.send_signal(signal.SIGINT)
  pdb.waitForPrompt()
  pdb.queryAndMatch("p i", "0")
  pdb.query("n")
  pdb.query("n")
  pdb.queryAndMatch("p i", "1")
  pdb.query("s")
  pdb.query("s")
  pdb.queryAndMatch("p i","2")
  pdb.pdbHandle.stdin.write("c\n")
  time.sleep(0.03)
  pdb.pdbHandle.send_signal(signal.SIGINT)
  pdb.waitForPrompt()
  pdb.queryAndMatch("p i","3")
msg101482 - (view) Author: Ilya Sandler (isandler) Date: 2010-03-22 05:56
I'm attaching a test for Ctrl-C behavior on Linux (the patch itself works on Windows too, but I am not sure how to send Ctrl-C on windows programatically and subprocess does not have this functionality either).

The test_pdb2.py module is generic and can be extended to test other functionality. But, as discussed earlier, it cannot be easily (if at all) integrated into existing test_pdb.py.

Note that the test module will NOT work with existing pdb (as it doesnot have expected Ctrl-C) behavior, but on can specify alternative pdb location from command line:

  env DEBUG_PDB=./pdb.py ./test_pdb2.py
msg101971 - (view) Author: Ilya Sandler (isandler) Date: 2010-03-31 02:27
Is there anything else I can do for this patch?
msg104739 - (view) Author: Ilya Sandler (isandler) Date: 2010-05-01 19:10
a note on testing: it should be possible to integrate the tests into existing test_pdb.py by simply placing subprocess based tests next to doctest-based tests. This way pdb tests will at least be in a single module. (this is an approach taken by a patch in 
http://bugs.python.org/issue7964)

Would it be better?
msg105348 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2010-05-08 23:39
Committed to trunk in r81012.  though as this missed 2.7beta2 its possible that will be rejected and this becomes a 3.x only feature.

I'm porting to py3k now.
msg105360 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2010-05-09 01:21
And reverted in trunk r81013.  Multiple buildbot problems from the initial commit due to the unittest.

This is likely to be py3k only at this point.

I do believe sig.patch.v3 is fine, but its the test_pdb2 unittest that is difficult to make work well.
msg106031 - (view) Author: Ilya Sandler (isandler) Date: 2010-05-19 03:48
I tried to understand the cause of failures and I don't see how test_pdb2 could have caused them ;-).

Here is the relevant part of buildbot timeline:

http://www.python.org/dev/buildbot/trunk/?last_time=1273368351&show_time=7400

The only test failures which have  log files were sparc solaris 10 and ia64 and both failures were in test_unittest. All other buildbot failures don't have test logs associated with them..

Is there a way to  access test logs for other platforms?
msg112107 - (view) Author: Georg Brandl (georg.brandl) * (Python committer) Date: 2010-07-30 22:21
I committed the "commands" part of the patch in r83308.

For the SIGINT part, I'm concerned that the SIGINT handler is set
in the Pdb constructor, since it's a processwide setting I think it
should a) be set only before sys.settrace() and b) be restored appropriately when debugging is finished.
msg112108 - (view) Author: Georg Brandl (georg.brandl) * (Python committer) Date: 2010-07-30 22:30
Adding the rest of the patch adapted for py3k trunk as of today.
msg122757 - (view) Author: Ilya Sandler (isandler) Date: 2010-11-29 01:48
The patch tries to write to stdout in signal handler. 

This currently does not work in the python 3.x  (see
http://bugs.python.org/issue10478).
msg123368 - (view) Author: Georg Brandl (georg.brandl) * (Python committer) Date: 2010-12-04 16:15
Committed rest of patch in r87045.  Will try to get test_pdb2 in shape and working on all platforms.
History
Date User Action Args
2010-12-04 16:15:23georg.brandlsetstatus: open -> closed
resolution: accepted
messages: + msg123368
2010-11-29 01:48:57isandlersetmessages: + msg122757
2010-07-30 22:30:11georg.brandlsetfiles: + pdb-keyboardinterrupt.diff
keywords: + patch
messages: + msg112108
2010-07-30 22:21:43georg.brandlsetnosy: + georg.brandl
messages: + msg112107
2010-05-19 03:48:25isandlersetmessages: + msg106031
2010-05-09 01:21:43gregory.p.smithsetmessages: + msg105360
versions: - Python 2.7
2010-05-08 23:39:38gregory.p.smithsetmessages: + msg105348
versions: + Python 3.2
2010-05-01 19:10:42isandlersetmessages: + msg104739
2010-03-31 02:27:14isandlersetmessages: + msg101971
2010-03-22 05:56:26isandlersetfiles: + test_pdb2.py

messages: + msg101482
2010-03-07 00:10:47isandlersetmessages: + msg100555
2010-03-06 19:27:43isandlersetmessages: + msg100543
2010-03-06 19:20:32isandlersetfiles: + sig.patch.v3.diff

messages: + msg100542
2010-02-28 20:23:29gregory.p.smithsetmessages: + msg100221
title: better Ctrl-C support in pdb (program can be resumed) -> better Ctrl-C support in pdb (program can be resumed) (issue216067)
2010-02-28 20:14:12gregory.p.smithsetmessages: + msg100220
2010-02-27 04:11:09isandlersetfiles: + sig.patch.v2

messages: + msg100177
2010-02-22 03:28:21r.david.murraysetnosy: + r.david.murray
messages: + msg99708
2010-02-22 03:00:51isandlersetmessages: + msg99706
2010-02-22 02:19:38isandlersetfiles: + sig.patch.v1

messages: + msg99702
2010-02-21 14:07:40gregory.p.smithsetpriority: normal

nosy: + gregory.p.smith
messages: + msg99661

assignee: gregory.p.smith
2009-11-01 22:44:20isandlersetmessages: + msg94811
2009-11-01 04:45:47draghuramsetnosy: + draghuram
messages: + msg94782
2009-10-31 19:37:55isandlercreate