[Python-checkins] r50588 - in sandbox/trunk/pdb: mpdb.py mthread.py test/test_mconnection.py test/test_mpdb.py

matt.fleming python-checkins at python.org
Tue Jul 11 21:33:40 CEST 2006


Author: matt.fleming
Date: Tue Jul 11 21:33:39 2006
New Revision: 50588

Modified:
   sandbox/trunk/pdb/mpdb.py
   sandbox/trunk/pdb/mthread.py
   sandbox/trunk/pdb/test/test_mconnection.py
   sandbox/trunk/pdb/test/test_mpdb.py
Log:
Additions to the threading code.


Modified: sandbox/trunk/pdb/mpdb.py
==============================================================================
--- sandbox/trunk/pdb/mpdb.py	(original)
+++ sandbox/trunk/pdb/mpdb.py	Tue Jul 11 21:33:39 2006
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
 # Pdb Improvements
 #
 # This is a Google Summer of Code project
@@ -23,6 +24,7 @@
 import traceback
 
 __all__ = ["MPdb", "pdbserver", "target", "thread_debugging"]
+__version__ = "0.1alpha"
 
 line_prefix = '\n-> '
 
@@ -380,8 +382,6 @@
     tcp = 'tcp mydomainname.com:9876'
     serial = 'serial /dev/ttyC0'
     """
-    m._program_sys_argv = list(m._sys_argv[2:])
-    m.mainpyfile = m._program_sys_argv[1]
     m.do_pdbserver(addr)
     while True:
         try:
@@ -409,16 +409,15 @@
 
 def thread_debugging(m):
     """ Setup this debugger to handle threaded applications."""
+    sys.path.append(os.path.dirname(m._sys_argv[1]))
     import mthread
     mthread.init(m)
-
-    m._program_sys_argv = list(m._sys_argv[2:])
-    m.mainpyfile = m._program_sys_argv[1]
     while True:
         try:
             m._runscript(m.mainpyfile)
             if m._user_requested_quit: break
         except Restart:
+            sys.argv = list(m._program_sys_argv)
             m.msg('Restarting')
         except:
             m.msg(traceback.format_exc())
@@ -426,23 +425,47 @@
 
 def main():
     """ Main entry point to this module. """
-    opts, args = parse_opts()
-
     mpdb = MPdb()
-    mpdb._sys_argv = ['python']
-    for i in sys.argv:
-        mpdb._sys_argv.append(i)
-    mpdb._program_sys_argv = mpdb._sys_argv[4:]
-    sys.argv = list(args)
-    if not opts.scriptname:
-        if not args and not opts.target:
-            print 'Error: mpdb.py must be called with a script name if ' \
-                  + '-p or -t switches are not specified.'
-            sys.exit(1)
-        elif not opts.target:
-            mainpyfile = args[0]
+    
+    from pydb.fns import process_options
+    from optparse import make_option
+
+    opt_list = [
+        make_option("-t", "--target", dest="target",
+                      help="Specify a target to connect to. The arguments" \
+                      + " should be of form, 'protocol address'."),
+        make_option("-p", "--pdbserver", dest="pdbserver",
+                      help="Start the debugger and execute the pdbserver " \
+                      + "command. The arguments should be of the form," \
+                      + " 'protocol address scriptname'."),
+        make_option("-d", "--debug-thread", action="store_true",
+                      help="Turn on thread debugging.")
+        ]
+        
+    opts = process_options(mpdb, "mpdb", os.path.basename(sys.argv[0])
+                           ,  __version__, opt_list)
+
+    tmp_argv = ['python']
+    for arg in mpdb._sys_argv:
+        tmp_argv.append(arg)
+
+    mpdb._sys_argv = list(tmp_argv)
+
+    if not sys.argv:
+        # No program to debug
+        mpdb.mainpyfile = None
     else:
-        mainpyfile = opts.scriptname
+        mpdb._program_sys_argv = list(sys.argv)
+
+        mpdb.mainpyfile = mpdb._program_sys_argv[0]
+        
+        if not os.path.exists(mpdb.mainpyfile):
+            print 'Error:', mpdb.mainpyfile, 'does not exist'
+
+        # Replace mpdb's dir with script's dir in front of
+        # module search path.
+        sys.path[0] = mpdb.main_dirname = os.path.dirname(mpdb.mainpyfile)
+
     if opts.target:
         target(opts.target)
         sys.exit()
@@ -452,17 +475,26 @@
     elif opts.debug_thread:
         thread_debugging(mpdb)
         sys.exit()
+
     while 1:
         try:
-            mpdb._runscript(mainpyfile)
+            if mpdb.mainpyfile:
+                mpdb._runscript(mpdb.mainpyfile)
+            else:
+                mpdb._wait_for_mainpyfile = True
+                mpdb.interaction(None, None)
+                
             if mpdb._user_requested_quit:
                 break
             mpdb.msg("The program finished and will be restarted")
         except Restart:
-            sys.argv = list(mpdb._program_sys_argv)
-            mpdb.msg('Restarting %s with arguments:\n\t%s'
-                     % (mpdb.filename(mainpyfile),
+            if mpdb._program_sys_argv:
+                sys.argv = list(mpdb._program_sys_argv)
+                mpdb.msg('Restarting %s with arguments:\n\t%s'
+                     % (mpdb.filename(mpdb.mainpyfile),
                         ' '.join(mpdb._program_sys_argv[1:])))
+            else: break
+            
         except SystemExit:
             # In most cases SystemExit does not warrant a post-mortem session.
             mpdb.msg("The program exited via sys.exit(). " + \
@@ -475,11 +507,11 @@
             mpdb.msg("Uncaught exception. Entering post mortem debugging")
             mpdb.msg("Running 'cont' or 'step' will restart the program")
             t = sys.exc_info()[2]
-            while t.tb_next is not None:
+            while t.tb_next != None:
                 t = t.tb_next
                 mpdb.interaction(t.tb_frame,t)
                 mpdb.msg("Post mortem debugger finished. The " + \
-                      mainpyfile + " will be restarted")
+                      mpdb.mainpyfile + " will be restarted")
 
 # Utility functions
 

Modified: sandbox/trunk/pdb/mthread.py
==============================================================================
--- sandbox/trunk/pdb/mthread.py	(original)
+++ sandbox/trunk/pdb/mthread.py	Tue Jul 11 21:33:39 2006
@@ -5,12 +5,17 @@
 import inspect
 import os
 import sys
+import thread
 import threading
 
+from pydb.fns import get_confirmation
 from pydb.gdb import Gdb
+from mpdb import MPdb, Exit    # Needed for calls to do_info
 
-# Global variables
-tt_dict = {} # Thread,tracer dictionary
+class UnknownThreadID(Exception): pass
+
+tt_dict = {}
+current_thread = None
 
 # This global variable keeps track of the 'main' debugger which, when one
 # of the MTracer objects encounters a breakpoint in its thread, is used to
@@ -23,8 +28,10 @@
 g_args = None
 g_breaks = {}
 g_commands = {}    # commands to execute at breakpoints
+g_dirname = None
 g_main_dirname = None
 g_program_sys_argv = None
+g_prompt = "(MPdb) "
 g_search_path = None
 g_sys_argv = None
 
@@ -37,7 +44,6 @@
     def __init__(self):
         Gdb.__init__(self, stdout=_main_debugger.stdout)
         self.thread = threading.currentThread()
-        self.prompt = '(MPdb: %s)' % self.thread.getName()
         self.running = True
         self.reset()
 
@@ -45,41 +51,80 @@
         self.breaks = dict(g_breaks)
         self._program_sys_argv = g_program_sys_argv
         self._sys_argv = g_sys_argv
+        self.prompt = g_prompt
 
         # XXX This needs fixing, hack so that when a new MTracer
         # is created for this thread we don't stop at the bottom
         # frame.
         self.botframe = 1
 
+    def trace_dispatch(self, frame, event, arg):
+        """ Override this method so that we can lock the main debugger
+        whilst we're performing our debugging operations.
+        """
+        if not self.running:
+            # The user has requested and confirmed a quit, nothing
+            # left to do but exit the entire debugger. Every
+            # thread calls sys.exit() in order to supress any
+            # exceptions.
+            sys.exit()
+            
+        _main_debugger.lock.acquire()
+        try:
+            # Normally exceptions just unwind up the execution
+            # stack and mpdb enters post mortem. It's more conveniant
+            # here to just see if an exception occurred and drop the
+            # user into a command loop.
+            if sys.exc_info()[2]:
+                self.user_exception(frame, sys.exc_info())
+                # Once we've finished accepting user commands, exit.
+                sys.exit()
+            Gdb.trace_dispatch(self, frame, event, arg)
+        finally:
+            _main_debugger.lock.release()
+
     def user_line(self, frame):
         """ Override this method from pydb.pydbbdb.Bdb to make
         it thread-safe.
         """
-        _main_debugger.lock.acquire()
+        _check_and_switch_current()
         Gdb.user_line(self, frame)
-        _main_debugger.lock.release()
   
     def user_call(self, frame, args):
         """ Override pydb.pydbbdb.Bdb.user_call and make it
         thread-safe.
         """
-        _main_debugger.lock.acquire()
+        _check_and_switch_current()
         Gdb.user_call(self, frame, args)
-        _main_debugger.lock.release()
 
-
-    def user_exception(self, frame, (type, value, traceback)):
+    def user_exception(self, frame, (exc_type, value, traceback)):
         """ Override this method from pydb.pydbbdb.Bdb to make
         it thread-safe.
         """
-        _main_debugger.lock.acquire()
-        Gdb.user_exception(self, frame, (type, value,
-                                              traceback))
-        _main_debugger.lock.release()
-
+        Gdb.msg(self, 'Program raised exception %s' % exc_type.__name__)
+        _check_and_switch_current()
+        Gdb.user_exception(self, frame, (exc_type, value,
+                                             traceback))
+            
     def do_thread(self, arg):
         do_thread(arg)
 
+    def do_info(self, arg):
+        do_info(arg)
+
+    def do_quit(self, arg):
+        """ Override this method to ask the user whether they really
+        want to quit.
+        """
+        if self.running:
+            ret = get_confirmation(self,
+                            'The program is running. Exit anyway? (y or n) ')
+            if ret:
+                for t in tt_dict.values():
+                    if t != self.thread:
+                        Gdb.do_quit(t, None)
+                sys.exit()
+            Gdb.msg(_main_debugger, 'Not confirmed.')
 
 def trace_dispatch_init(frame, event, arg):
     """ This method is called by a sys.settrace when a thread is running
@@ -94,7 +139,6 @@
     tt_dict[th] = tr
 
     sys.settrace(tr.trace_dispatch)
-    
 
 def init(debugger):
     """ This method intialises thread debugging. It sets up a tracing
@@ -103,9 +147,13 @@
     the debugger that is debugging the MainThread, i.e. the Python
     interpreter.
     """
-    global _main_debugger, g_breaks
+    global _main_debugger, g_breaks, g_commands, g_dirname, g_program_sys_argv
+    global g_search_path, g_sys_argv, current_thread
+    
     if _main_debugger == None:
         _main_debugger = debugger
+
+        current_thread = thread.get_ident()
         
         # This lock must be acquired when a MTracer object
         # places a call to _main_debugger.user_*
@@ -116,6 +164,7 @@
         g_breaks = _main_debugger.breaks
         g_commands = _main_debugger.commands
         g_dirname = _main_debugger.main_dirname
+        g_prompt = _main_debugger.prompt
         g_program_sys_argv = _main_debugger._program_sys_argv
         g_search_path = _main_debugger.search_path
         g_sys_argv = _main_debugger._sys_argv
@@ -128,41 +177,69 @@
 
         _main_debugger.do_break = do_break
         _main_debugger.do_thread = do_thread
+        _main_debugger.do_info = do_info
 
     global tt_dict
     tt_dict[threading.currentThread().getName()] = _main_debugger
     threading.settrace(trace_dispatch_init)
 
+def _check_and_switch_current():
+        """ Check to see if we're the current thread. If we're not,
+        we change the current_thread global variable to indicate that
+        now, we are the current thread.
+        """
+        global current_thread
+        if current_thread == thread.get_ident():
+            return
+        else:
+            Gdb.msg(_main_debugger, '\n[Switching to Thread %s]' %
+                    thread.get_ident())
+            current_thread = thread.get_ident()
+
 # All the methods below override the methods from MPdb so
 # that they are thread-safe. Every thread must contend for
 # the Lock object on the MPdb instance.
 
 def user_line(frame):
     _main_debugger.lock.acquire()
-    Gdb.user_line(_main_debugger, frame)
-    _main_debugger.lock.release()
+    try:
+        _check_and_switch_current()
+        Gdb.user_line(_main_debugger, frame)
+    finally:
+        _main_debugger.lock.release()
+
 
 def user_call(frame, arg):
     _main_debugger.lock.acquire()
-    Gdb.user_call(_main_debugger, frame, arg)
-    _main_debugger.lock.release()
-
+    try:
+        _check_and_switch_current()
+        Gdb.user_call(_main_debugger, frame, arg)
+    finally:
+        _main_debugger.lock.release()
+    
 def user_return(frame, return_value):
     _main_debugger.lock.acquire()
-    Gdb.user_return(_main_debugger, frame, return_value)
-    _main_debugger.lock.release()
-
+    try:
+        _check_and_switch_current()
+        Gdb.user_return(_main_debugger, frame, return_value)
+    finally:
+       _main_debugger.lock.release()
+    
 def user_exception(frame, (exc_type, exc_value, exc_traceback)):
     _main_debugger.lock.acquire()
-    Gdb.user_exception(_main_debugger, frame, (exc_type, exc_value,
+    try:
+        _check_and_switch_current()
+        Gdb.user_exception(_main_debugger, frame, (exc_type, exc_value,
                                                exc_traceback))
-    _main_debugger.lock.release()
+    finally:
+        _main_debugger.lock.release()
 
 def do_break(arg, temporary=0):
     """Override the break command so that, by default,
     breakpoints are set in all threads.
     """
     global tt_dict
+
     if 'thread' in arg:
         # We're setting a thread-specific breakpoint
         # syntax: break linespec thread threadID
@@ -171,13 +248,9 @@
             t = tt_dict[threadID]
         except KeyError:
             Gdb.errmsg(_main_debugger, 'Invalid thread ID')
-            return
-        
-    Gdb.do_break(_main_debugger, arg, temporary)
+        return
 
-    if len(tt_dict) > 1:
-        for tracer in tt_dict.values():
-            tracer.do_break(arg, temporary)
+    Gdb.do_break(_main_debugger, arg, temporary)
     
 def do_thread(arg):
     """Use this command to switch between threads.
@@ -190,10 +263,12 @@
 Type "help thread" followed by thread subcommand name for full documentation.
 Command name abbreviations are allowed if unambiguous.
 """
+    global tt_dict
     if not arg:
-        global tt_dict
-        for t in tt_dict.keys():
-            Gdb.msg(_main_debugger, t)
+        Gdb.msg(_main_debugger,
+                '[Current thread is (Thread %s)]' %
+                thread.get_ident())
+        return
 
     if 'apply' in arg:
         threadID = arg[6:arg.find(' ', 6)]
@@ -205,6 +280,40 @@
             tr.onecmd(cmd)
         except KeyError:
             return
-                       
-        
+
+def print_thread_details():
+    # The output here should be as close to the expected output of
+    # 'thread' in GDB. Each message consits of,
+    #  - A thread number assigned by mpdb
+    #  - Stack frame summary
+    threads = sys._current_frames()
+
+    i = 0
+    for t in threads.keys():
+        f = threads[t]
+        s = ""
+        global current_thread
+        if t == current_thread:
+            s += "* "
+        else: s += "  "
+        s += str(t) + " in " + str(f.f_code.co_name) + \
+             "() at " + f.f_code.co_filename + ":" + str(f.f_lineno)
+        Gdb.msg(_main_debugger, s)
+
+
+# Info subcommand and helper functions
+def do_info(arg):
+    """ Extends mpdb.do_info to include information about
+    thread commands.
+    """
+    if not arg:
+        Gdb.do_info(_main_debugger, arg)
+        return
+
+    args = arg.split()
+    if 'thread'.startswith(args[0]) and len(args[0]) > 2:
+        print_thread_details()
+    else:
+        Gdb.do_info(_main_debugger, arg)
+
 

Modified: sandbox/trunk/pdb/test/test_mconnection.py
==============================================================================
--- sandbox/trunk/pdb/test/test_mconnection.py	(original)
+++ sandbox/trunk/pdb/test/test_mconnection.py	Tue Jul 11 21:33:39 2006
@@ -251,6 +251,7 @@
     def testInvalidPipe(self):
         """(FIFO) Connect to an invalid named pipe."""
         self.assertRaises(ConnectionFailed,self.client.connect, 'invalid')
+        os.unlink('invalid0')
 
     def tearDown(self):
         self.client.disconnect()

Modified: sandbox/trunk/pdb/test/test_mpdb.py
==============================================================================
--- sandbox/trunk/pdb/test/test_mpdb.py	(original)
+++ sandbox/trunk/pdb/test/test_mpdb.py	Tue Jul 11 21:33:39 2006
@@ -31,6 +31,7 @@
     for i in range(MAXTRIES):
         try:
             client.connection.connect(address)
+            if client.connection.connected: break
         except ConnectionFailed, e:
             # This is the error message we expect i.e. when the server thread
             # hasn't started yet. Otherwise it's probably a _real_ error


More information about the Python-checkins mailing list