[Python-checkins] r47283 - in sandbox/trunk/pdb: Doc/lib/libmpdb.tex README.txt mconnection.py mpdb.py mthread.py test/test_mconnection.py test/test_mpdb.py test/test_mthread.py test/thread_script.py

matt.fleming python-checkins at python.org
Fri Jul 7 00:27:50 CEST 2006


Author: matt.fleming
Date: Fri Jul  7 00:27:49 2006
New Revision: 47283

Modified:
   sandbox/trunk/pdb/Doc/lib/libmpdb.tex
   sandbox/trunk/pdb/README.txt
   sandbox/trunk/pdb/mconnection.py
   sandbox/trunk/pdb/mpdb.py
   sandbox/trunk/pdb/mthread.py
   sandbox/trunk/pdb/test/test_mconnection.py
   sandbox/trunk/pdb/test/test_mpdb.py
   sandbox/trunk/pdb/test/test_mthread.py
   sandbox/trunk/pdb/test/thread_script.py
Log:
Fix tests cases. When using TCP as a pdbserver connection, setsockopt's
REUSEADDR is on by default. Add some documentation regarding threading and
a little bit on remote debugging. Update README with some things to finish.
Change the the thread_script.py test file so it raises an exception.


Modified: sandbox/trunk/pdb/Doc/lib/libmpdb.tex
==============================================================================
--- sandbox/trunk/pdb/Doc/lib/libmpdb.tex	(original)
+++ sandbox/trunk/pdb/Doc/lib/libmpdb.tex	Fri Jul  7 00:27:49 2006
@@ -1023,6 +1023,15 @@
 This section provides information on Python's thread debugging facilities and
 how \module{mpdb} makes use of them.
 
+\module{mpdb} uses a master-slaves system for handling thread debugging. The
+main debugger (master) is the instance of \class{MPdb} that is
+used by the Python 
+Interpreter. The thread that runs the Python Interpreter is a special class
+called \class{_MainThread}. Whenever we refer to the main debugger, this is 
+what we are refering to. The \class{MTracer} objects (slaves) 
+are responsible for tracing threads that are created in the script being
+debugged.
+
 % How does Python facilitate debugging threads
 \subsection{Python's Thread Debugging Features}
 
@@ -1030,8 +1039,26 @@
 allows threads to be debugged.
 
 There are two modules in the Python Standard Library that provide thread
-functionality, \ulink{\module{thread}}{http://docs.python.org/lib/module-thread.html} and \ulink{\module{threading}}{http://docs.python.org/lib/module-threading.html}. Threads created with the \module{threading} module can be traced by
-calling \code{threading.settrace()}.
+functionality, \ulink{\module{thread}}{http://docs.python.org/lib/module-thread.html} and \ulink{\module{threading}}{http://docs.python.org/lib/module-threading.html}. 
+There are three types of threads in Python,
+
+- threads created with the \module{thread} module. These are low-level threads.
+
+- threads created with the \module{threading} module. These are high-level
+  threads.
+
+- the \class{_MainThread}. This thread is the main thread, it is created
+when the Python interpreter is started. All signals go to this thread.
+
+
+The global trace function for threads created with the \module{threading}
+module can be set by calling \function{threading.settrace()}. We can use
+this method to inspect the frame objects on the stack, for example, to find
+the filename and line number of the code object currently executing. See
+\ulink{The Python Library Reference}
+{http://docs.python.org/lib/debugger-hooks.html} for documentation on how
+debugger's use the global trace function.
+
 
 \subsection{\module{mpdb}'s Thread Debugging Facilities}
 
@@ -1039,8 +1066,7 @@
 on whilst inside the debugger by issuing the command \code{set thread}.
 
 \emph{All the information below can be considered the internals of how mpdb
-works. In other words, this information doesn't provide useful information
-for someone simply wishing to use the debugger for thread debugging.}
+works. It is a programmer's reference.}
 
 When thread debugging is activated in \module{mpdb}, it imports the
 \module{mthread} module and calls that modules \function{init()} function,
@@ -1052,17 +1078,30 @@
 documentation in the Python Standard Library for more information on how
 debuggers interact with trace functions.
 
-An \class{MTracer} object provides three methods for dealing with the different
-possible events in that occur in the frame object.
-
+An \class{MTracer} object provides methods for dealing with the different
+possible events in that occur in the frame object \ref{mtracer-objects}.
 
+The main debugger is repsonsible for debugging the \class{_MainThread} object
+and the \class{MTracer} objects are responsible for debugging all other
+threads. When a thread needs to stop exection, it acquires the main debugger's
+\member{lock} variable, which is a \class{Lock} object. The \function{acquire}
+call will block until the lock is obtained. When this lock is obtained 
+execution transfers to the thread the \class{MTracer} is running in. This
+means that this thread is now the current thread.
 
 
 \section{Remote Debugging}
-This section describes how \module{mpdb} handles debugging remotely.
 \label{remote-debug}
+This section describes how \module{mpdb} handles debugging remotely.
+
+Remote debugging in \module{mpdb} takes place between a \emph{pdbserver}
+and a client. The pdbserver is analogous to GDB's gdbserver. A pdbserver
+is run on the same machine as the program being debugged. This pdbserver
+then allows clients to connect to it which begins a debugging session. In
+this session all commands come from the client and are executed on the 
+pdbserver.
 
 \section{External Process Debugging}
+\label{proc-debug}
 This section describes how \module{mpdb} debugs processes that are external
 to the process in which \module{mpdb} is being run.
-\label{proc-debug}
\ No newline at end of file

Modified: sandbox/trunk/pdb/README.txt
==============================================================================
--- sandbox/trunk/pdb/README.txt	(original)
+++ sandbox/trunk/pdb/README.txt	Fri Jul  7 00:27:49 2006
@@ -54,4 +54,8 @@
       `run 2' does not pass the argument 2 to the script.
 * Allow thread debugging to be turned on with a command line switch.
 * Allow reading commands from .mpdbrc file 
-* Changed the name of the default history file from ~/.pydbhist to ~/.mpdbhist
\ No newline at end of file
+* Changed the name of the default history file from ~/.pydbhist to ~/.mpdbhist
+* do_return inherited from pydb.gdb.Gdb doesn't use self.stdin for reading
+  input from the user and doesn't work remotely.
+* pdbserver using a TCP connection uses setsockopt() REUSEADDR by default.
+  Need some way to make this configurable. `set reuseaddr' ?
\ No newline at end of file

Modified: sandbox/trunk/pdb/mconnection.py
==============================================================================
--- sandbox/trunk/pdb/mconnection.py	(original)
+++ sandbox/trunk/pdb/mconnection.py	Fri Jul  7 00:27:49 2006
@@ -100,7 +100,7 @@
         self._sock = self.output = self.input = None
 	MConnectionInterface.__init__(self)
         
-    def connect(self, addr, reuseaddr=False):
+    def connect(self, addr, reuseaddr=True):
         """Set to allow a connection from a client. 'addr' specifies
         the hostname and port combination of the server.
         """

Modified: sandbox/trunk/pdb/mpdb.py
==============================================================================
--- sandbox/trunk/pdb/mpdb.py	(original)
+++ sandbox/trunk/pdb/mpdb.py	Fri Jul  7 00:27:49 2006
@@ -18,10 +18,8 @@
 from optparse import OptionParser
 import pydb
 from pydb.gdb import Restart
-import socket
 import sys
 import time
-import threading
 import traceback
 
 __all__ = ["MPdb", "pdbserver", "target"]
@@ -407,6 +405,26 @@
         except:
             break
 
+def thread_debugging(m):
+    """ Setup this debugger to handle threaded applications."""
+    import mthread
+    mthread.init(m)
+    m.user_line = mthread.user_line
+    m.user_call = mthread.user_call
+    m.user_exception = mthread.user_exception
+    m.user_return = mthread.user_return
+    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:
+            m.msg('Restarting')
+        except:
+            m.msg(traceback.format_exc())
+            break
+
 def main():
     """ Main entry point to this module. """
     opts, args = parse_opts()
@@ -432,7 +450,9 @@
     elif opts.pdbserver:
         pdbserver(opts.pdbserver, mpdb)
         sys.exit()
-
+    elif opts.debug_thread:
+        thread_debugging(mpdb)
+        sys.exit()
     while 1:
         try:
             mpdb._runscript(mainpyfile)
@@ -477,6 +497,8 @@
                       help="Start the debugger and execute the pdbserver " \
                       + "command. The arguments should be of the form," \
                       + " 'protocol address scriptname'.")
+    parser.add_option("-d", "--debug-thread", action="store_true",
+                      help="Turn on thread debugging.")
     (options, args) = parser.parse_args()
     return (options, args)
 

Modified: sandbox/trunk/pdb/mthread.py
==============================================================================
--- sandbox/trunk/pdb/mthread.py	(original)
+++ sandbox/trunk/pdb/mthread.py	Fri Jul  7 00:27:49 2006
@@ -10,8 +10,7 @@
 from pydb.gdb import Gdb
 
 # Global variables
-tracers = []       # The MTracer objects that are tracing threads
-threads = []       # List of threads we're tracing
+tt_dict = {} # Thread,tracer dictionary
 
 # 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
@@ -26,73 +25,37 @@
     a thread's run() method.
     """
     def __init__(self):
-        Gdb.__init__(self)
+        Gdb.__init__(self, stdout=_main_debugger.stdout)
         self.thread = threading.currentThread()
-        # Each tracer instance must keep track of its own breakpoints
-        self.fncache = {}
-        self.lineno = 0
-        self.curframe = None
-        self.stopframe = None
-        self.botframe = None
-        self.quitting = False
-
-    def trace_dispatch(self, frame, event, arg):
-        self.currentframe = frame
-        if event == 'line':
-            return self.dispatch_line
-        if event == 'call':
-            self.msg('%s *** call' % self.thread.getName())
-            return self.trace_dispatch
-        if event == 'return':
-            self.msg('%s *** return' % self.thread.getName())
-            return self.trace_dispatch
-        if event == 'exception':
-            self.msg('%s *** exception' % self.thread.getName())
-            return self.trace_dispatch
-        if event == 'c_call':
-            print '*** c_call'
-            return self.trace_dispatch
-        if event == 'c_exception':
-            print '*** c_exception'
-            return self.trace_dispatch
-        if event == 'c_return':
-            print '*** c_return'
-            return self.trace_dispatch
-        print 'bdb.Bdb.dispatch: unknown debugging event:', repr(event)
-        return self.trace_dispatch
-
-    def dispatch_line(self, frame, arg):
-        if self.stop_here(frame) or self.break_here(frame):
-            # Signal to the main debugger that we've hit a breakpoint
-            print 'bang'
-            _main_debugger.user_line(frame)
-            if self.quitting: raise BdbQuit
-        return self.trace_dispatch
-
-    def dispatch_call(self, frame, arg):
-        # XXX 'arg' is no longer used
-        if self.botframe is None:
-            # First call of dispatch since reset()
-            self.botframe = frame.f_back # (CT) Note that this may also be None!
-            return self.trace_dispatch
-        if not (self.stop_here(frame) or self.break_anywhere(frame)):
-            # No need to trace this function
-            return # None
-        _main_debugger.user_call(frame, arg)
-        if self.quitting: raise BdbQuit
-        return self.trace_dispatch
-
-    def dispatch_return(self, frame, arg):
-        if self.stop_here(frame) or frame == self.returnframe:
-            _main_debugger.user_return(frame, arg)
-            if self.quitting: raise BdbQuit
-        return self.trace_dispatch
-
-    def dispatch_exception(self, frame, arg):
-        if self.stop_here(frame):
-            _main_debugger.user_exception(frame, arg)
-            if self.quitting: raise BdbQuit
-        return self.trace_dispatch
+        self.prompt = '(MPdb: %s)' % self.thread.getName()
+        self.running = True
+        self.reset()
+        
+    def user_line(self, frame):
+        """ Override this method from pydb.pydbbdb.Bdb to make
+        it thread-safe.
+        """
+        _main_debugger.lock.acquire()
+        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()
+        Gdb.user_call(self, frame, args)
+        _main_debugger.lock.release()
+
+    def user_exception(self, frame, (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()
+
 
 def trace_dispatch_init(frame, event, arg):
     """ This method is called by a sys.settrace when a thread is running
@@ -100,13 +63,14 @@
     set this thread's tracing function to that object's trace_dispatch
     method.
     """
-    global threads, tracers
-    threads.append(threading.currentThread())
-    t = MTracer()
-
-    tracers.append(t)
-    threading.settrace(t.trace_dispatch)
-    sys.settrace(t.trace_dispatch)
+    global tt_dict
+    tr = MTracer()
+    th = threading.currentThread()
+
+    tt_dict[th] = tr
+
+    threading.settrace(tr.trace_dispatch)
+    sys.settrace(tr.trace_dispatch)
     
 
 def init(debugger):
@@ -117,11 +81,39 @@
     interpreter.
     """
     global _main_debugger
-    _main_debugger = debugger
-    threading.settrace(trace_dispatch_init)
+    if _main_debugger == None:
+        _main_debugger = debugger
+        
+        # This lock must be acquired when a MTracer object
+        # places a call to _main_debugger.user_*
+        _main_debugger.lock = threading.Lock()
     
+    threading.settrace(trace_dispatch_init)
 
-
+# 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()
+
+def user_call(frame, arg):
+    _main_debugger.lock.acquire()
+    Gdb.user_call(_main_debugger, frame, arg)
+    _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()
+
+def user_exception(frame, (exc_type, exc_value, exc_traceback)):
+    _main_debugger.lock.acquire()
+    Gdb.user_exception(_main_debugger, frame, (exc_type, exc_value,
+                                               exc_traceback))
+    _main_debugger.lock.release()
 
 
 

Modified: sandbox/trunk/pdb/test/test_mconnection.py
==============================================================================
--- sandbox/trunk/pdb/test/test_mconnection.py	(original)
+++ sandbox/trunk/pdb/test/test_mconnection.py	Fri Jul  7 00:27:49 2006
@@ -42,6 +42,8 @@
         thread.start_new_thread(repeatedConnect, (self.client, __addr__))
         self.server.connect(__addr__)
 
+	self.server.disconnect()
+
     def testClientConnectAndRead(self):
         """(tcp) Connect to server and read/write. """
         thread.start_new_thread(repeatedConnect, (self.client,__addr__))
@@ -70,6 +72,8 @@
         line = self.server.readline()
         self.assertEquals('good\n', line, 'Could not read first line.')
 
+	self.server.disconnect()
+
     def testErrorAddressAlreadyInUse(self):
         """(tcp) Test address already in use error. """
         thread.start_new_thread(repeatedConnect, (self.client, __addr__))

Modified: sandbox/trunk/pdb/test/test_mpdb.py
==============================================================================
--- sandbox/trunk/pdb/test/test_mpdb.py	(original)
+++ sandbox/trunk/pdb/test/test_mpdb.py	Fri Jul  7 00:27:49 2006
@@ -93,7 +93,7 @@
     def testTarget(self):
         """ Test the target command. """
         server = MConnectionServerTCP()
-        thread.start_new_thread(server.connect, (__addr__,))
+        thread.start_new_thread(server.connect, (__addr__,True))
 
         self.client1 = MPdbTest()
         connect_to_target(self.client1)
@@ -168,6 +168,7 @@
         server.target = 'remote'
         self.client1.onecmd('restart')
         self.client1.connection.write('rquit\n')
+        server.disconnect()
 
         for i in range(MAXTRIES):
             if server.connection != None: pass

Modified: sandbox/trunk/pdb/test/test_mthread.py
==============================================================================
--- sandbox/trunk/pdb/test/test_mthread.py	(original)
+++ sandbox/trunk/pdb/test/test_mthread.py	Fri Jul  7 00:27:49 2006
@@ -7,14 +7,13 @@
 from test import test_support
 
 sys.path.append('..')
-import mthread
+import mthread, mpdb
 
 class TestThreadDebugging(unittest.TestCase):
     def testMthreadInit(self):
         """ Test the init method of the mthread file. """
-        m = sys.stdout.write
-        e = sys.stderr.write
-        mthread.init(m, e)
+        m = mpdb.MPdb()
+        mthread.init(m)
 
 def test_main():
     test_support.run_unittest(TestThreadDebugging)

Modified: sandbox/trunk/pdb/test/thread_script.py
==============================================================================
--- sandbox/trunk/pdb/test/thread_script.py	(original)
+++ sandbox/trunk/pdb/test/thread_script.py	Fri Jul  7 00:27:49 2006
@@ -5,14 +5,19 @@
 
 import threading
 
+def foo():
+    l = [i for i in range(10)]
+    for n in l:
+        print l[n+1]
+        
 class MyThread(threading.Thread):
     def run(self):
-        for i in range(10):
-            print i
-
+        foo()
+        
 def func():
-    t = MyThread()
-    t.start()
+    for i in range(2):
+        t = MyThread()
+        t.start()
     t.join()
 
 if __name__ == '__main__':


More information about the Python-checkins mailing list