Author: Armin Rigo <ar...@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 _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit