[pypy-commit] pypy reverse-debugger: Trying to support pressing Ctrl-C to interrupt the current operation

arigo pypy.commits at gmail.com
Fri Aug 12 16:54:09 EDT 2016


Author: Armin Rigo <arigo at tunes.org>
Branch: reverse-debugger
Changeset: r86180:2311c3b8a675
Date: 2016-08-12 22:44 +0200
http://bitbucket.org/pypy/pypy/changeset/2311c3b8a675/

Log:	Trying to support pressing Ctrl-C to interrupt the current operation

diff --git a/rpython/translator/revdb/interact.py b/rpython/translator/revdb/interact.py
--- a/rpython/translator/revdb/interact.py
+++ b/rpython/translator/revdb/interact.py
@@ -35,59 +35,79 @@
         self.print_extra_pending_info = None
 
     def interact(self):
-        last_command = 'help'
-        previous_time = None
-        previous_thread = 0
+        self.last_command = 'help'
+        self.previous_time = None
+        self.previous_thread = 0
         while True:
+            prompt = self.print_lines_before_prompt()
+            try:
+                while True:
+                    cmdline = self.display_prompt(prompt)
+                    self.run_command(cmdline)
+                    prompt = self.print_lines_before_prompt()
+            except KeyboardInterrupt:
+                self.pgroup.recreate_subprocess(self.previous_time or 1)
+                self.last_command = ''
+                self.previous_thread = '?'
+                self.previous_time = '?'
+
+    def print_lines_before_prompt(self):
+        last_time = self.pgroup.get_current_time()
+        if last_time != self.previous_time:
+            print
+            if self.pgroup.get_current_thread() != self.previous_thread:
+                self.previous_thread = self.pgroup.get_current_thread()
+                if self.previous_thread == 0:
+                    print ('-------------------- in main thread #0 '
+                           '--------------------')
+                else:
+                    print ('-------------------- in non-main thread '
+                           '#%d --------------------' % (self.previous_thread,))
+            self.pgroup.update_watch_values()
             last_time = self.pgroup.get_current_time()
-            if last_time != previous_time:
-                print
-                if self.pgroup.get_current_thread() != previous_thread:
-                    previous_thread = self.pgroup.get_current_thread()
-                    if previous_thread == 0:
-                        print ('-------------------- in main thread #0 '
-                               '--------------------')
-                    else:
-                        print ('-------------------- in non-main thread '
-                               '#%d --------------------' % (previous_thread,))
-                self.pgroup.update_watch_values()
-                last_time = self.pgroup.get_current_time()
-            if self.print_extra_pending_info:
-                print self.print_extra_pending_info
-                self.print_extra_pending_info = None
-            if last_time != previous_time:
-                self.pgroup.show_backtrace(complete=0)
-                previous_time = last_time
+        if self.print_extra_pending_info:
+            print self.print_extra_pending_info
+            self.print_extra_pending_info = None
+        if last_time != self.previous_time:
+            self.pgroup.show_backtrace(complete=0)
+            self.previous_time = last_time
+        prompt = '(%d)$ ' % last_time
+        return prompt
 
-            prompt = '(%d)$ ' % last_time
+    def display_prompt(self, prompt):
+        try:
+            cmdline = raw_input(prompt).strip()
+        except EOFError:
+            print
+            cmdline = 'quit'
+        if not cmdline:
+            cmdline = self.last_command
+        return cmdline
+
+    def run_command(self, cmdline):
+        match = r_cmdline.match(cmdline)
+        if not match:
+            return
+        self.last_command = cmdline
+        command, argument = match.groups()
+        try:
+            runner = getattr(self, 'command_' + command)
+        except AttributeError:
+            print >> sys.stderr, "no command '%s', try 'help'" % (command,)
+        else:
             try:
-                cmdline = raw_input(prompt).strip()
-            except EOFError:
-                print
-                cmdline = 'quit'
-            if not cmdline:
-                cmdline = last_command
-            match = r_cmdline.match(cmdline)
-            if not match:
-                continue
-            last_command = cmdline
-            command, argument = match.groups()
-            try:
-                runner = getattr(self, 'command_' + command)
-            except AttributeError:
-                print >> sys.stderr, "no command '%s', try 'help'" % (command,)
-            else:
-                try:
-                    runner(argument)
-                except Exception as e:
-                    traceback.print_exc()
-                    print >> sys.stderr
-                    print >> sys.stderr, 'Something went wrong.  You are now',
-                    print >> sys.stderr, 'in a pdb; press Ctrl-D to continue.'
-                    import pdb; pdb.post_mortem(sys.exc_info()[2])
-                    print >> sys.stderr
-                    print >> sys.stderr, 'You are back running %s.' % (
-                        sys.argv[0],)
+                runner(argument)
+            except KeyboardInterrupt:
+                raise
+            except Exception as e:
+                traceback.print_exc()
+                print >> sys.stderr
+                print >> sys.stderr, 'Something went wrong.  You are now',
+                print >> sys.stderr, 'in a pdb; press Ctrl-D to continue.'
+                import pdb; pdb.post_mortem(sys.exc_info()[2])
+                print >> sys.stderr
+                print >> sys.stderr, 'You are back running %s.' % (
+                    sys.argv[0],)
 
     def command_help(self, argument):
         """Display commands summary"""
diff --git a/rpython/translator/revdb/process.py b/rpython/translator/revdb/process.py
--- a/rpython/translator/revdb/process.py
+++ b/rpython/translator/revdb/process.py
@@ -132,11 +132,11 @@
         self.currently_created_objects = msg.arg2
         self.current_thread = msg.arg3
 
-    def clone(self):
+    def clone(self, activate=False):
         """Fork this subprocess.  Returns a new ReplayProcess() that is
         an identical copy.
         """
-        self.send(Message(CMD_FORK))
+        self.send(Message(CMD_FORK, int(activate)))
         s1, s2 = socket.socketpair()
         ancillary.send_fds(self.control_socket.fileno(), [s2.fileno()])
         s2.close()
@@ -459,7 +459,7 @@
         clone_me = self.paused[from_time]
         if self.active is not None:
             self.active.close()
-        self.active = clone_me.clone()
+        self.active = clone_me.clone(activate=True)
 
     def jump_in_time(self, target_time):
         """Jump in time at the given 'target_time'.
@@ -561,11 +561,13 @@
             self.active.send(Message(CMD_ATTACHID, nid, uid, int(watch_env)))
             self.active.expect_ready()
 
-    def recreate_subprocess(self):
-        # recreate a subprocess at the current time
-        time = self.get_current_time()
+    def recreate_subprocess(self, target_time=None):
+        # recreate a subprocess at the given time, or by default the
+        # current time
+        if target_time is None:
+            target_time = self.get_current_time()
         self.active = None
-        self.jump_in_time(time)
+        self.jump_in_time(target_time)
 
     def print_cmd(self, expression, nids=[]):
         """Print an expression.
diff --git a/rpython/translator/revdb/src-revdb/revdb.c b/rpython/translator/revdb/src-revdb/revdb.c
--- a/rpython/translator/revdb/src-revdb/revdb.c
+++ b/rpython/translator/revdb/src-revdb/revdb.c
@@ -1249,7 +1249,7 @@
     exit(0);
 }
 
-static void command_fork(void)
+static void command_fork(int activate)
 {
     int child_sockfd;
     int child_pid;
@@ -1272,6 +1272,11 @@
         }
         rpy_rev_sockfd = child_sockfd;
 
+        /* The 'activate' flag of CMD_FORK tells if the child process
+           must die or not when receiving SIGINT.  Active children
+           die; inactive children (stored in 'pgroup.paused') don't. */
+        signal(SIGINT, activate ? SIG_DFL : SIG_IGN);
+
         /* Close and re-open the revdb log file in the child process.
            This is the simplest way I found to give 'rpy_rev_fileno'
            its own offset, independent from the parent.  It assumes
@@ -1422,7 +1427,7 @@
             switch (cmd.cmd) {
 
             case CMD_FORK:
-                command_fork();
+                command_fork(cmd.arg1);
                 break;
 
             case CMD_QUIT:
diff --git a/rpython/translator/revdb/test/ctrl_c.py b/rpython/translator/revdb/test/ctrl_c.py
new file mode 100644
--- /dev/null
+++ b/rpython/translator/revdb/test/ctrl_c.py
@@ -0,0 +1,43 @@
+import sys, os, thread, time, signal
+
+os.setpgid(0, 0)
+assert os.getpgrp() == os.getpid()
+
+
+sys.path[:] = sys.argv[1].split('\x7f')
+from rpython.translator.revdb.process import ReplayProcessGroup
+
+exename, rdbname = sys.argv[2:]
+group = ReplayProcessGroup(exename, rdbname)
+
+
+class MyInterrupt(Exception):
+    pass
+def my_signal(*args):
+    raise MyInterrupt
+prev_signal = signal.signal(signal.SIGINT, my_signal)
+
+def enable_timer():
+    def my_kill():
+        time.sleep(0.8)
+        print >> sys.stderr, "--<<< Sending CTRL-C >>>--"
+        os.killpg(os.getpid(), signal.SIGINT)
+    thread.start_new_thread(my_kill, ())
+
+all_ok = False
+try:
+    # this runs for ~9 seconds if uninterrupted
+    enable_timer()
+    group.print_cmd('very-long-loop')
+except MyInterrupt:
+    print >> sys.stderr, "very-long-loop interrupted, trying again"
+    group.recreate_subprocess(1)
+    try:
+        enable_timer()
+        group.print_cmd('very-long-loop')
+    except MyInterrupt:
+        print >> sys.stderr, "second interruption ok"
+        all_ok = True
+
+assert all_ok, "expected very-long-loop to be killed by SIGINT"
+print "all ok"
diff --git a/rpython/translator/revdb/test/test_process.py b/rpython/translator/revdb/test/test_process.py
--- a/rpython/translator/revdb/test/test_process.py
+++ b/rpython/translator/revdb/test/test_process.py
@@ -1,4 +1,4 @@
-import py, sys, math
+import py, sys, math, os, subprocess, time
 from cStringIO import StringIO
 from rpython.rlib import revdb, rdtoa
 from rpython.rlib.debug import debug_print, ll_assert
@@ -56,6 +56,14 @@
                 revdb.send_output(rdtoa.dtoa(xx) + '\n')
                 revdb.send_output('%d\n' % yy)
                 return
+            elif extra == 'very-long-loop':
+                i = 0
+                total = 0
+                while i < 2000000000:
+                    total += revdb.flag_io_disabled()
+                    i += 1
+                revdb.send_output(str(total))
+                return
             else:
                 assert False
             uid = revdb.get_unique_id(stuff)
@@ -214,3 +222,16 @@
         with stdout_capture() as buf:
             group.print_cmd('2.35')
         assert buf.getvalue() == "0.35\n2.0\n0.5875\n2\n"
+
+    def test_ctrl_c(self):
+        localdir = os.path.dirname(__file__)
+        args = [sys.executable, os.path.join(localdir, 'ctrl_c.py'),
+                '\x7f'.join(sys.path),
+                str(self.exename), self.rdbname]
+        t1 = time.time()
+        result = subprocess.check_output(args)
+        t2 = time.time()
+        print 'subprocess returned with captured stdout:\n%r' % (result,)
+        assert result == 'all ok\n'
+        # should take two times ~0.8 seconds if correctly interrupted
+        assert t2 - t1 < 3.0


More information about the pypy-commit mailing list