[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