[Python-checkins] r50505 - in sandbox/trunk/pdb: README.txt mconnection.py mpdb.py mthread.py test/test_mconnection.py

matt.fleming python-checkins at python.org
Mon Jul 10 03:53:39 CEST 2006


Author: matt.fleming
Date: Mon Jul 10 03:53:38 2006
New Revision: 50505

Modified:
   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
Log:
Added FIFOs and unit tests. Some changes to the threading code.


Modified: sandbox/trunk/pdb/README.txt
==============================================================================
--- sandbox/trunk/pdb/README.txt	(original)
+++ sandbox/trunk/pdb/README.txt	Mon Jul 10 03:53:38 2006
@@ -33,7 +33,7 @@
 * Provide a proper top-level methods including, set_trace(), post_mortem(),
 	   run(), remote_sighandler() (for allowing a signal to start
 	   remote debugging)
-* Extend mconnection FIFO's
+* Clean up FIFO output (too many newlines)
 * mconnection should use the exceptions that have been introduced and mpdb
   should check for these exceptions being raised.
 * Write documentation
@@ -58,4 +58,4 @@
 * 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
+  Need some way to make this configurable. `set reuseaddr' ?

Modified: sandbox/trunk/pdb/mconnection.py
==============================================================================
--- sandbox/trunk/pdb/mconnection.py	(original)
+++ sandbox/trunk/pdb/mconnection.py	Mon Jul 10 03:53:38 2006
@@ -201,4 +201,101 @@
         self._sock = None
         self.connected = False
 
+import os
+class MConnectionServerFIFO(MConnectionInterface):
+    """ This class implements a named pipe for communication between
+    a pdbserver and client.
+    """
+    def __init__(self):
+        MConnectionInterface.__init__(self)
+        self.input = self.output = self._filename = self._mode = None
 
+    def connect(self, name, mode=0644):
+        self._filename = name
+        self._file_in = self._filename+'0'
+        self._file_out = self._filename+'1'
+        self._mode = mode
+        try:
+            os.mkfifo(self._file_in, self._mode)
+            os.mkfifo(self._file_out, self._mode)
+        except OSError, e:
+            raise ConnectionFailed, e[1]
+        self.input = open(self._file_in, 'r')
+        self.output = open(self._file_out, 'w')
+
+    def disconnect(self):
+        """ Disconnect from the named pipe. """
+        if not self.input or not self.output:
+            return
+        self.output.close()
+        self.input.close()
+        self.input = self.output = None
+        os.unlink(self._file_in)
+        os.unlink(self._file_out)
+
+
+    def readline(self):
+        """ Read a line from the named pipe. """
+        try:
+            # Using readline allows the data to be read quicker, don't
+            # know why.
+            line = self.input.readline()
+        except IOError, e:
+            raise ReadError, e[1]
+        if not line:
+            raise ReadError, 'Connection closed'
+        return line
+
+    def write(self, msg):
+        if msg[-1] != '\n': msg += '\n'
+        try:
+            self.output.write(msg)
+            self.output.flush()
+        except IOError, e:
+            raise WriteError, e[1]
+
+class MConnectionClientFIFO(MConnectionInterface):
+    """ This class is the client class for accessing a named pipe
+    used for communication between client and pdbserver.
+    """
+    def __init__(self):
+        MConnectionInterface.__init__(self)
+        self.input = self.output = self._filename = self._mode = None
+        
+    def connect(self, name, mode=0644):
+        self._filename = name
+        self._file_in = self._filename+'1'
+        self._file_out = self._filename+'0'
+        self._mode = mode
+        try:
+            self.output = open(self._file_out, 'w')
+            self.input = open(self._file_in, 'r')
+        except IOError, e:
+            raise ConnectionFailed, e[1]
+        
+    def disconnect(self):
+        if not self.input or not self.output:
+            return
+        self.output.close()
+        self.input.close()
+        self.input = self.output = None
+
+    def readline(self):
+        try:
+            line = self.input.readline()
+        except IOError, e:
+            raise ReadError, e[1]
+        if not line:
+            raise ReadError, 'Connection closed'
+        return line
+
+    def write(self, msg):
+        if msg[-1] != '\n': msg += '\n'
+        try:
+            self.output.write(msg)
+            self.output.flush()
+        except IOError, e:
+            raise WriteError, e[1]
+        
+        
+        

Modified: sandbox/trunk/pdb/mpdb.py
==============================================================================
--- sandbox/trunk/pdb/mpdb.py	(original)
+++ sandbox/trunk/pdb/mpdb.py	Mon Jul 10 03:53:38 2006
@@ -55,6 +55,7 @@
         self.target = 'local'  # local connections by default
         self.lastcmd = ''
         self.connection = None
+        self.debugger_name = 'mpdb'
         self._info_cmds.append('target')
 
     def _rebind_input(self, new_input):
@@ -221,24 +222,10 @@
                 self.errmsg('Could not import MConnectionSerial')
                 return
         else:
-            if '.' in target:
-                if self.connection: self.connection.disconnect()
-                # We dynamically load the class for the connection
-                base = target[:target.rfind('.')]
-                cls = target[target.rfind('.')+1:]
-                try:
-                    exec 'from ' + base + ' import (' + cls + ', \
-                    ConnectionFailed)'
-                except ImportError:
-                    self.errmsg('Unknown target type')
-                    return 
-            else:
-                try:
-                    __import__(target)
-                except ImportError:
-                    self.errmsg('Unknown target type')
-                    return 
-            self.connection = eval(target+'()')
+            cls = target[target.rfind('.')+1:]
+            path = target[:target.rfind('.')]
+            exec 'from ' + path + ' import ' + cls + ', ConnectionFailed'
+            self.connection = eval(cls+'()')
         try:
             self.connection.connect(addr)
         except ConnectionFailed, err:
@@ -409,10 +396,7 @@
     """ 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:

Modified: sandbox/trunk/pdb/mthread.py
==============================================================================
--- sandbox/trunk/pdb/mthread.py	(original)
+++ sandbox/trunk/pdb/mthread.py	Mon Jul 10 03:53:38 2006
@@ -18,6 +18,16 @@
 # said thread stopped at.
 _main_debugger = None
 
+# These global variables keep track of 'global state' in the
+# debugger.
+g_args = None
+g_breaks = {}
+g_commands = {}    # commands to execute at breakpoints
+g_main_dirname = None
+g_program_sys_argv = None
+g_search_path = None
+g_sys_argv = None
+
 class MTracer(Gdb):
     """ A class to trace a thread. Breakpoints can be passed from
     a main debugger to this debugger through the constructor
@@ -30,7 +40,17 @@
         self.prompt = '(MPdb: %s)' % self.thread.getName()
         self.running = True
         self.reset()
-        
+
+        # Copy some state
+        self.breaks = dict(g_breaks)
+        self._program_sys_argv = g_program_sys_argv
+        self._sys_argv = g_sys_argv
+
+        # 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 user_line(self, frame):
         """ Override this method from pydb.pydbbdb.Bdb to make
         it thread-safe.
@@ -47,6 +67,7 @@
         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.
@@ -56,6 +77,9 @@
                                               traceback))
         _main_debugger.lock.release()
 
+    def do_thread(self, arg):
+        do_thread(arg)
+
 
 def trace_dispatch_init(frame, event, arg):
     """ This method is called by a sys.settrace when a thread is running
@@ -65,11 +89,10 @@
     """
     global tt_dict
     tr = MTracer()
-    th = threading.currentThread()
+    th = threading.currentThread().getName()
 
     tt_dict[th] = tr
 
-    threading.settrace(tr.trace_dispatch)
     sys.settrace(tr.trace_dispatch)
     
 
@@ -80,14 +103,34 @@
     the debugger that is debugging the MainThread, i.e. the Python
     interpreter.
     """
-    global _main_debugger
+    global _main_debugger, g_breaks
     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()
-    
+
+        # Copy some state from the main debugger so that 
+        # newly created debuggers can have the same.
+        g_breaks = _main_debugger.breaks
+        g_commands = _main_debugger.commands
+        g_dirname = _main_debugger.main_dirname
+        g_program_sys_argv = _main_debugger._program_sys_argv
+        g_search_path = _main_debugger.search_path
+        g_sys_argv = _main_debugger._sys_argv
+
+        # Replace some of the mpdb methods with thread-safe ones
+        _main_debugger.user_line = user_line
+        _main_debugger.user_call = user_call
+        _main_debugger.user_exception = user_exception
+        _main_debugger.user_return = user_return
+
+        _main_debugger.do_break = do_break
+        _main_debugger.do_thread = do_thread
+
+    global tt_dict
+    tt_dict[threading.currentThread().getName()] = _main_debugger
     threading.settrace(trace_dispatch_init)
 
 # All the methods below override the methods from MPdb so
@@ -115,7 +158,53 @@
                                                exc_traceback))
     _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
+        args, threadcmd, threadID = arg.split()
+        try:
+            t = tt_dict[threadID]
+        except KeyError:
+            Gdb.errmsg(_main_debugger, 'Invalid thread ID')
+            return
+        
+    Gdb.do_break(_main_debugger, arg, temporary)
 
-
-
+    if len(tt_dict) > 1:
+        for tracer in tt_dict.values():
+            tracer.do_break(arg, temporary)
+    
+def do_thread(arg):
+    """Use this command to switch between threads.
+The new thread ID must be currently known.
+
+List of thread subcommands:
+
+thread apply -- Apply a command to a list of threads
+
+Type "help thread" followed by thread subcommand name for full documentation.
+Command name abbreviations are allowed if unambiguous.
+"""
+    if not arg:
+        global tt_dict
+        for t in tt_dict.keys():
+            Gdb.msg(_main_debugger, t)
+
+    if 'apply' in arg:
+        threadID = arg[6:arg.find(' ', 6)]
+        cmd = arg[7+len(threadID):]
+        global tt_dict
+        try:
+            tr = tt_dict[threadID]
+            # XXX Maybe this wants to be, tr.do_cmd(params)?
+            tr.onecmd(cmd)
+        except KeyError:
+            return
+                       
+        
 

Modified: sandbox/trunk/pdb/test/test_mconnection.py
==============================================================================
--- sandbox/trunk/pdb/test/test_mconnection.py	(original)
+++ sandbox/trunk/pdb/test/test_mconnection.py	Mon Jul 10 03:53:38 2006
@@ -19,7 +19,9 @@
 
 sys.path.append("..")
 from mconnection import (MConnectionServerTCP, MConnectionClientTCP,
-                         MConnectionSerial, ConnectionFailed, ReadError)
+                         MConnectionSerial, MConnectionServerFIFO,
+                         MConnectionClientFIFO, ConnectionFailed,
+                         ReadError, WriteError)
 
 # Try to connect the client to addr either until we've tried MAXTRIES
 # times or until it succeeds.
@@ -100,13 +102,13 @@
         """(tcp) Test the ReadError exception."""
         thread.start_new_thread(self.server.connect, (__addr__,))
 
-        while self.server._sock == None:
+        while not self.server._sock:
             pass
             
         repeatedConnect(self.client, __addr__)
 
         # Wait to make _absolutely_ sure that the client has connected
-        while self.server.output == None:
+        while not self.server.output:
             pass
         self.client.disconnect()
         self.assertRaises(ReadError, self.server.readline)
@@ -180,9 +182,84 @@
         self.client.disconnect()
         os.remove(TESTFN)
 
+class TestFIFOConnections(unittest.TestCase):
+    def setUp(self):
+        self.server = MConnectionServerFIFO()
+        self.client = MConnectionClientFIFO()
+
+    def testConnect(self):
+        """(FIFO) Connect a client to a server. """
+        thread.start_new_thread(self.client.connect, ('test_file',))
+        self.server.connect('test_file')
+
+    def testReadWrite(self):
+        """(FIFO) Test reading and writing to and from server/client."""
+        thread.start_new_thread(self.client.connect, ('test_file',))
+        self.server.connect('test_file')
+
+        # Server write, client read
+        self.server.write('Tim The Enchanter!\n')
+
+        # Wait for the thread to catch up
+        while not self.client.input:
+            pass
+        line = self.client.readline()
+        self.assertEquals('Tim The Enchanter!\n', line)
+
+        # Client write, server read
+        self.client.write('received\n')
+        line = self.server.readline()
+        self.assertEquals('received\n', line)
+
+    def testMultipleDisconnect(self):
+        """(FIFO) Disconnect disconnected connections."""
+        self.client.disconnect()
+        self.server.disconnect()
+
+    def testReadError(self):
+        """(FIFO) Test ReadError."""
+        thread.start_new_thread(self.client.connect, ('test_file',))
+        self.server.connect('test_file')
+
+        while not self.client.input:
+            pass
+
+        self.client.disconnect()
+        self.assertRaises(ReadError, self.server.readline)
+
+        self.client.connect('test_file')
+
+        self.server.disconnect()
+        self.assertRaises(ReadError, self.client.readline)
+
+    def testWriteError(self):
+        """(FIFO) Test WriteError."""
+        thread.start_new_thread(self.client.connect, ('test_file',))
+        self.server.connect('test_file')
+
+        while not self.client.input:
+            pass
+
+        self.client.disconnect()
+        self.assertRaises(WriteError, self.server.write, 'spam\n')
+
+        self.client.connect('test_file')
+
+        self.server.disconnect()
+        self.assertRaises(WriteError, self.client.write, 'Ni!\n')
+
+    def testInvalidPipe(self):
+        """(FIFO) Connect to an invalid named pipe."""
+        self.assertRaises(ConnectionFailed,self.client.connect, 'invalid')
+
+    def tearDown(self):
+        self.client.disconnect()
+        self.server.disconnect()
+
         
 def test_main():
-    test_support.run_unittest(TestTCPConnections, TestSerialConnections)
+    test_support.run_unittest(TestTCPConnections, TestSerialConnections,
+                              TestFIFOConnections)
     
 if __name__ == '__main__':
     test_main()


More information about the Python-checkins mailing list