[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