Эх сурвалжийг харах

[desktop] Extract thread stacktrace printing

This also removes the threadframe dependency, which was deprecated
with the release of Python 2.5.
Erick Tryzelaar 10 жил өмнө
parent
commit
369fd7b

+ 0 - 18
desktop/core/ext-py/threadframe-0.2/GNUmakefile.mingw2

@@ -1,18 +0,0 @@
-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

+ 0 - 10
desktop/core/ext-py/threadframe-0.2/Makefile

@@ -1,10 +0,0 @@
-all:
-	python setup.py build
-
-install:
-	python setup.py install
-
-clean:
-	python setup.py clean
-	-rm -rf build
-	-rm -f core *~ *.so *.o *.pyd *.a python*.def

+ 0 - 25
desktop/core/ext-py/threadframe-0.2/README

@@ -1,25 +0,0 @@
-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.

+ 0 - 37
desktop/core/ext-py/threadframe-0.2/sample.txt

@@ -1,37 +0,0 @@
-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)

+ 0 - 21
desktop/core/ext-py/threadframe-0.2/setup.py

@@ -1,21 +0,0 @@
-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'],
-        ),
-    ],
-)

+ 0 - 57
desktop/core/ext-py/threadframe-0.2/test.py

@@ -1,57 +0,0 @@
-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)

+ 0 - 3
desktop/core/ext-py/threadframe-0.2/threadframe.def

@@ -1,3 +0,0 @@
-EXPORTS
-	initthreadframe
-

+ 0 - 111
desktop/core/ext-py/threadframe-0.2/threadframemodule.c

@@ -1,111 +0,0 @@
-/*
- * 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);
-}

+ 41 - 0
desktop/core/src/desktop/lib/thread_util.py

@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+import threading
+import traceback
+
+def dump_traceback(file=sys.stderr, all_threads=True):
+  """Print a thread stacktrace"""
+
+  current_thread = threading.current_thread()
+
+  if all_threads:
+    threads = threading.enumerate()
+  else:
+    threads = [current_thread]
+
+  for thread in threads:
+    if thread == current_thread:
+      name = "Current thread"
+    else:
+      name = "Thread"
+
+    print >> file, "%s %s %s (most recent call last):" % (name, thread.name, thread.ident)
+    frame = sys._current_frames()[thread.ident]
+    traceback.print_stack(frame, file=file)
+    print >> file

+ 54 - 0
desktop/core/src/desktop/lib/thread_util_test.py

@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+# Licensed to Cloudera, Inc. under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  Cloudera, Inc. licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import StringIO
+import threading
+import time
+
+from nose.tools import assert_true
+from desktop.lib.thread_util import dump_traceback
+
+def test_dump_traceback():
+  started = threading.Event()
+  stop = threading.Event()
+
+  class Thread(threading.Thread):
+    def run(self):
+      started.set()
+      assert_true(stop.wait(1.0))
+
+  thread = Thread(name='thread_util_test thread')
+  thread.start()
+  thread_ident = str(thread.ident)
+
+  header = 'Thread thread_util_test thread %s' % thread_ident
+
+  try:
+    assert_true(started.wait(1.0))
+
+    out = StringIO.StringIO()
+    dump_traceback(file=out)
+
+    assert_true(header in out.getvalue())
+
+    out = StringIO.StringIO()
+    dump_traceback(file=out, all_threads=False)
+
+    assert_true(header not in out.getvalue())
+  finally:
+    stop.set()
+    thread.join()

+ 4 - 9
desktop/core/src/desktop/tests.py

@@ -69,18 +69,13 @@ def teardown_test_environment():
   themselves and leaving threads hanging around.
   """
   import threading
+  import desktop.lib.thread_util
+
   # We should shut down all relevant threads by test completion.
   threads = list(threading.enumerate())
 
-  try:
-    import threadframe
-    import traceback
-    if len(threads) > 1:
-      for v in threadframe.dict().values():
-        traceback.print_stack(v)
-  finally:
-    # threadframe is only available in the dev build.
-    pass
+  if len(threads) > 1:
+    desktop.lib.thread_util.dump_traceback()
 
   assert 1 == len(threads), threads
 

+ 6 - 8
desktop/core/src/desktop/views.py

@@ -15,6 +15,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import StringIO
 import json
 import logging
 import os
@@ -45,6 +46,7 @@ from desktop.lib.conf import GLOBAL_CONFIG
 from desktop.lib.django_util import login_notrequired, render_json, render
 from desktop.lib.i18n import smart_str
 from desktop.lib.paths import get_desktop_root
+from desktop.lib.thread_util import dump_traceback
 from desktop.log.access import access_log_level, access_warn
 from desktop.models import UserPreferences, Settings
 from desktop import appmanager
@@ -225,14 +227,10 @@ def threads(request):
   if not request.user.is_superuser:
     return HttpResponse(_("You must be a superuser."))
 
-  out = []
-  for thread_id, stack in sys._current_frames().iteritems():
-    out.append("Thread id: %s" % thread_id)
-    for filename, lineno, name, line in traceback.extract_stack(stack):
-      out.append("  %-20s %s(%d)" % (name, filename, lineno))
-      out.append("    %-80s" % (line))
-    out.append("")
-  return HttpResponse("\n".join(out), content_type="text/plain")
+  out = StringIO.StringIO()
+  dump_traceback(file=out)
+
+  return HttpResponse(out.getvalue(), content_type="text/plain")
 
 @access_log_level(logging.WARN)
 def memory(request):

+ 0 - 1
desktop/devtools.mk

@@ -24,7 +24,6 @@ DEVTOOLS += \
 	nose \
 	coverage \
 	nosetty \
-	threadframe \
 	werkzeug \
 	windmill