From 95c06280ca214fe13570e5aebd0d40857f90610c Mon Sep 17 00:00:00 2001 From: Dan Williams Date: Thu, 17 Aug 2006 03:05:44 +0000 Subject: Add threadframe and TracebackUtils.py so we can get tracebacks of dbus deadlocks --- diff --git a/Makefile.am b/Makefile.am index d1b0640..5d9791f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = activities shell sugar tools +SUBDIRS = activities shell sugar tools threadframe dbusconfdir = $(pkgdatadir) dbusconf_DATA = dbus-installed.conf diff --git a/configure.ac b/configure.ac index f53cb50..95c4c46 100644 --- a/configure.ac +++ b/configure.ac @@ -43,4 +43,5 @@ sugar/presence/Makefile po/Makefile.in tools/Makefile tools/sugar-setup-activity +threadframe/Makefile ]) diff --git a/sugar/Makefile.am b/sugar/Makefile.am index 086d291..6fad678 100644 --- a/sugar/Makefile.am +++ b/sugar/Makefile.am @@ -8,6 +8,7 @@ sugar_PYTHON = \ env.py \ logger.py \ setup.py \ + TracebackUtils.py \ util.py EXTRA_DIST = __uninstalled__.py diff --git a/sugar/TracebackUtils.py b/sugar/TracebackUtils.py new file mode 100644 index 0000000..3973da1 --- /dev/null +++ b/sugar/TracebackUtils.py @@ -0,0 +1,37 @@ +import sys +import traceback +import os +import signal + +haveThreadframe = True +try: + import threadframe +except ImportError: + haveThreadframe = False + +class TracebackHelper(object): + def __init__(self): + fname = "%s-%d" % (os.path.basename(sys.argv[0]), os.getpid()) + self._fpath = os.path.join("/tmp", fname) + print "Tracebacks will be written to %s on SIGUSR1" % self._fpath + signal.signal(signal.SIGUSR1, self._handler) + + def __del__(self): + try: + os.remove(self._fpath) + except OSError: + pass + + def _handler(self, signum, pframe): + f = open(self._fpath, "a") + if not haveThreadframe: + f.write("Threadframe not installed. No traceback available.\n") + else: + frames = threadframe.dict() + for thread_id, frame in frames.iteritems(): + f.write(('-' * 79) + '\n') + f.write('[Thread %s] %d' % (thread_id, sys.getrefcount(frame)) + '\n') + traceback.print_stack(frame, limit=None, file=f) + f.write("\n") + f.write('\n') + f.close() diff --git a/threadframe/GNUmakefile.mingw2 b/threadframe/GNUmakefile.mingw2 new file mode 100644 index 0000000..f5c0df4 --- /dev/null +++ b/threadframe/GNUmakefile.mingw2 @@ -0,0 +1,18 @@ +PYTHON:= $(shell python -c "import sys;print '%%d%%d' %% sys.version_info[:2]") + +threadframe.pyd: threadframe.o libpython$(PYTHON).a + dllwrap --dllname threadframe.pyd --driver-name=gcc --def threadframe.def -o threadframe.pyd threadframe.o -s --entry _DllMain@12 --target=i386-mingw32 -L. -lpython$(PYTHON) + +threadframe.o: threadframemodule.c + gcc -I"C:\Program Files\Python$(PYTHON)\include" -O3 -c -o $@ -DNDEBUG $< +libpython$(PYTHON).a: python$(PYTHON).def C:\WINNT\system32\python$(PYTHON).dll + dlltool --dllname python$(PYTHON).dll --def python$(PYTHON).def --output-lib libpython$(PYTHON).a + +python$(PYTHON).def: C:\WINNT\system32\python$(PYTHON).dll + pexports C:\WINNT\system32\python$(PYTHON).dll > python$(PYTHON).def + +clean: + -del threadframe.pyd + -del libpython$(PYTHON).a + -del threadframe.o + -del python$(PYTHON).def diff --git a/threadframe/README b/threadframe/README new file mode 100644 index 0000000..ec63fa2 --- /dev/null +++ b/threadframe/README @@ -0,0 +1,34 @@ +Note on the License +Dan Williams 2006-08-16 + +Since 'setup.py' specifies the "Python" license, it is assumed that the +threadframe package is distributed under that license, even though there +is no license header at the top of the source file. + + + +Obtaining tracebacks on other threads in Python +=============================================== +by Fazal Majid (www.majid.info), 2004-06-10 + +David Beazley added advanced debugging functions to the Python interpreter, +and they have been folded into the 2.2 release. Guido van Rossum added in +Python 2.3 the thread ID to the interpreter state structure, and this allows +us to produce a dictionary mapping thread IDs to frames. + +I used these hooks to build a debugging module that is useful when you +are looking for deadlocks in a multithreaded application. I've built +and tested this only on Solaris 8/x86, but the code should be pretty +portable. + +Of course, I disclaim any liability if this code should crash your system, +erase your homework, eat your dog (who also ate your homework) or otherwise +have any undesirable effect. + +Building and installing +======================= + +Download threadframe-0.2.tar.gz. You can use the Makefile or the setup.py +script. There is a small test program test.py that illustrates how to use this +module to dump stack frames of all the Python interpreter threads. A sample +run is available for your perusal. diff --git a/threadframe/sample.txt b/threadframe/sample.txt new file mode 100644 index 0000000..f5444d8 --- /dev/null +++ b/threadframe/sample.txt @@ -0,0 +1,37 @@ +Script started on Thu 10 Jun 2004 07:23:38 PM PDT +bayazid ~/threadframe-0.2>python test.py +ident of main thread is: 1 + +launching daemon thread... done +launching self-deadlocking thread... done +launching thread that will die before the end... done +[4] Spam spam spam spam. Lovely spam! Wonderful spam! +[4] Spam spam spam spam. Lovely spam! Wonderful spam! +[4] Spam spam spam spam. Lovely spam! Wonderful spam! +[4] Spam spam spam spam. Lovely spam! Wonderful spam! +------------------------------------------------------------------------ +[1] 4 + File "test.py", line 56, in ? + traceback.print_stack(frame) +------------------------------------------------------------------------ +[4] 4 + File "/usr/local/lib/python2.3/threading.py", line 436, in __bootstrap + self.run() + File "test.py", line 6, in run + time.sleep(1) +------------------------------------------------------------------------ +[5] 4 + File "/usr/local/lib/python2.3/threading.py", line 436, in __bootstrap + self.run() + File "test.py", line 13, in run + U_lock.acquire() +------------------------------------------------------------------------ +[6] 3 + File "/usr/local/lib/python2.3/threading.py", line 455, in __bootstrap + pass + File "test.py", line 20, in run + V_event.wait() + File "/usr/local/lib/python2.3/threading.py", line 352, in wait + self.__cond.release() + File "/usr/local/lib/python2.3/threading.py", line 235, in wait + self._acquire_restore(saved_state) diff --git a/threadframe/setup.py b/threadframe/setup.py new file mode 100644 index 0000000..df8f46d --- /dev/null +++ b/threadframe/setup.py @@ -0,0 +1,21 @@ +from distutils.core import setup +from distutils.extension import Extension + +setup( + name = 'threadframe', + version = '0.2', + description = "Advanced thread debugging extension", + long_description = "Obtaining tracebacks on other threads than the current thread", + url = 'http://www.majid.info/mylos/stories/2004/06/10/threadframe.html', + maintainer = 'Fazal Majid', + maintainer_email = 'threadframe@majid.info', + license = 'Python', + platforms = [], + keywords = ['threading', 'thread'], + + ext_modules=[ + Extension('threadframe', + ['threadframemodule.c'], + ), + ], +) diff --git a/threadframe/test.py b/threadframe/test.py new file mode 100644 index 0000000..6f1c82a --- /dev/null +++ b/threadframe/test.py @@ -0,0 +1,57 @@ +import sys, time, threading, thread, os, traceback, threadframe, pprint +# daemon thread that spouts Monty Pythonesque nonsense +class T(threading.Thread): + def run(self): + while 1: + time.sleep(1) + print '[%d] Spam spam spam spam. Lovely spam! Wonderful spam!' % ( thread.get_ident(), ) +# thread that cause a deliberate deadlock with itself +U_lock = threading.Lock() +class U(threading.Thread): + def run(self): + U_lock.acquire() + U_lock.acquire() +# thread that will exit after the thread frames are extracted but before +# they are printed +V_event = threading.Event() +class V(threading.Thread): + def run(self): + V_event.clear() + V_event.wait() + +print 'ident of main thread is: %d' % (thread.get_ident(),) +print +print 'launching daemon thread...', +T().start() +print 'done' +print 'launching self-deadlocking thread...', +U().start() +print 'done' +print 'launching thread that will die before the end...', +v = V() +v.start() +print 'done' + +time.sleep(5) + +# Python 2.2 does not support threadframe.dict() +if sys.hexversion < 0x02030000: + frames = threadframe.threadframe() +else: + frames = threadframe.dict() + +# signal the thread V to die, then wait for it to oblige +V_event.set() +v.join() + +if sys.hexversion < 0x02030000: + for frame in frames: + print '-' * 72 + print 'frame ref count = %d' % sys.getrefcount(frame) + traceback.print_stack(frame) +else: + for thread_id, frame in frames.iteritems(): + print '-' * 72 + print '[%s] %d' % (thread_id, sys.getrefcount(frame)) + traceback.print_stack(frame) +os._exit(0) diff --git a/threadframe/threadframe.def b/threadframe/threadframe.def new file mode 100644 index 0000000..713e4b2 --- /dev/null +++ b/threadframe/threadframe.def @@ -0,0 +1,3 @@ +EXPORTS + initthreadframe + diff --git a/threadframe/threadframemodule.c b/threadframe/threadframemodule.c new file mode 100644 index 0000000..2f67a45 --- /dev/null +++ b/threadframe/threadframemodule.c @@ -0,0 +1,111 @@ +/* + * module to access the stack frame of all Python interpreter threads + * + * works on Solaris and OS X, portability to other OSes unknown + * + * Fazal Majid, 2002-10-11 + * + * with contributions from Bob Ippolito (http://bob.pycs.net/) + * + * Copyright (c) 2002-2004 Kefta Inc. + * All rights reserved + * + */ + +#include "Python.h" +#include "compile.h" +#include "frameobject.h" +#include "patchlevel.h" + +static PyObject * +threadframe_threadframe(PyObject *self, PyObject *args) { + PyInterpreterState *interp; + PyThreadState *tstate; + PyFrameObject *frame; + PyListObject *frames; + + frames = (PyListObject*) PyList_New(0); + if (! frames) return NULL; + + /* Walk down the interpreters and threads until we find the one + matching the supplied thread ID. */ + for (interp = PyInterpreterState_Head(); interp != NULL; + interp = interp->next) { + for(tstate = interp->tstate_head; tstate != NULL; + tstate = tstate->next) { + frame = tstate->frame; + if (! frame) continue; + Py_INCREF(frame); + PyList_Append((PyObject*) frames, (PyObject*) frame); + } + } + return (PyObject*) frames; +} + +/* the PyThreadState gained a thread_id member only in 2.3rc1 */ +static PyObject * +threadframe_dict(PyObject *self, PyObject *args) { +#if PY_VERSION_HEX < 0x02030000 + PyErr_SetString(PyExc_NotImplementedError, + "threadframe.dict() requires Python 2.3 or later"); + return NULL; +#else + PyInterpreterState *interp; + PyThreadState *tstate; + PyFrameObject *frame; + PyObject *frames; + + frames = (PyObject*) PyDict_New(); + if (! frames) return NULL; + + /* Walk down the interpreters and threads until we find the one + matching the supplied thread ID. */ + for (interp = PyInterpreterState_Head(); interp != NULL; + interp = interp->next) { + for(tstate = interp->tstate_head; tstate != NULL; + tstate = tstate->next) { + PyObject *thread_id; + frame = tstate->frame; + if (! frame) continue; + thread_id = PyInt_FromLong(tstate->thread_id); + PyDict_SetItem(frames, thread_id, (PyObject*)frame); + Py_DECREF(thread_id); + } + } + return frames; +#endif +} + +static char threadframe_doc[] = +"Returns a list of frame objects for all threads.\n" +"(equivalent to dict().values() on 2.3 and later)."; + +static char threadframe_dict_doc[] = +"Returns a dictionary, mapping for all threads the thread ID\n" +"(as returned by thread.get_ident() or by the keys to threading._active)\n" +"to the corresponding frame object.\n" +"Raises NotImplementedError on Python 2.2."; + +/* List of functions defined in the module */ + +static PyMethodDef threadframe_methods[] = { + {"threadframe", threadframe_threadframe, METH_VARARGS, threadframe_doc}, + {"dict", threadframe_dict, METH_VARARGS, threadframe_dict_doc}, + {NULL, NULL} /* sentinel */ +}; + + +/* Initialization function for the module (*must* be called initthreadframe) */ + +static char module_doc[] = +"Debugging module to extract stack frames for all Python interpreter heads.\n" +"Useful in conjunction with traceback.print_stack().\n"; + +DL_EXPORT(void) +initthreadframe(void) +{ + PyObject *m; + + /* Create the module and add the functions */ + m = Py_InitModule3("threadframe", threadframe_methods, module_doc); +} -- cgit v0.9.1