mirror of https://github.com/python/cpython
After approval from Anthony, merge the tim-current_frames
branch into the trunk. This adds a new sys._current_frames() function, which returns a dict mapping thread id to topmost thread stack frame.
This commit is contained in:
parent
2b221ed657
commit
32a8361f2d
|
@ -41,7 +41,7 @@ It is always available.
|
|||
\code{Include/patchlevel.h} if the branch is a tag. Otherwise,
|
||||
it is \code{None}.
|
||||
\versionadded{2.5}
|
||||
\end{datadesc}
|
||||
\end{datadesc}
|
||||
|
||||
\begin{datadesc}{builtin_module_names}
|
||||
A tuple of strings giving the names of all modules that are compiled
|
||||
|
@ -55,6 +55,23 @@ It is always available.
|
|||
interpreter.
|
||||
\end{datadesc}
|
||||
|
||||
\begin{funcdesc}{_current_frames}{}
|
||||
Return a dictionary mapping each thread's identifier to the topmost stack
|
||||
frame currently active in that thread at the time the function is called.
|
||||
Note that functions in the \refmodule{traceback} module can build the
|
||||
call stack given such a frame.
|
||||
|
||||
This is most useful for debugging deadlock: this function does not
|
||||
require the deadlocked threads' cooperation, and such threads' call stacks
|
||||
are frozen for as long as they remain deadlocked. The frame returned
|
||||
for a non-deadlocked thread may bear no relationship to that thread's
|
||||
current activity by the time calling code examines the frame.
|
||||
|
||||
This function should be used for internal and specialized purposes
|
||||
only.
|
||||
\versionadded{2.5}
|
||||
\end{funcdesc}
|
||||
|
||||
\begin{datadesc}{dllhandle}
|
||||
Integer specifying the handle of the Python DLL.
|
||||
Availability: Windows.
|
||||
|
@ -142,7 +159,7 @@ It is always available.
|
|||
function, \function{exc_info()} will return three \code{None} values until
|
||||
another exception is raised in the current thread or the execution stack
|
||||
returns to a frame where another exception is being handled.
|
||||
|
||||
|
||||
This function is only needed in only a few obscure situations. These
|
||||
include logging and error handling systems that report information on the
|
||||
last or current exception. This function can also be used to try to free
|
||||
|
@ -241,7 +258,7 @@ It is always available.
|
|||
\begin{itemize}
|
||||
\item On Windows 9x, the encoding is ``mbcs''.
|
||||
\item On Mac OS X, the encoding is ``utf-8''.
|
||||
\item On Unix, the encoding is the user's preference
|
||||
\item On Unix, the encoding is the user's preference
|
||||
according to the result of nl_langinfo(CODESET), or None if
|
||||
the nl_langinfo(CODESET) failed.
|
||||
\item On Windows NT+, file names are Unicode natively, so no conversion
|
||||
|
@ -279,8 +296,8 @@ It is always available.
|
|||
\end{funcdesc}
|
||||
|
||||
\begin{funcdesc}{getwindowsversion}{}
|
||||
Return a tuple containing five components, describing the Windows
|
||||
version currently running. The elements are \var{major}, \var{minor},
|
||||
Return a tuple containing five components, describing the Windows
|
||||
version currently running. The elements are \var{major}, \var{minor},
|
||||
\var{build}, \var{platform}, and \var{text}. \var{text} contains
|
||||
a string while all other values are integers.
|
||||
|
||||
|
@ -491,7 +508,7 @@ else:
|
|||
be registered using \function{settrace()} for each thread being
|
||||
debugged. \note{The \function{settrace()} function is intended only
|
||||
for implementing debuggers, profilers, coverage tools and the like.
|
||||
Its behavior is part of the implementation platform, rather than
|
||||
Its behavior is part of the implementation platform, rather than
|
||||
part of the language definition, and thus may not be available in
|
||||
all Python implementations.}
|
||||
\end{funcdesc}
|
||||
|
|
|
@ -171,6 +171,11 @@ PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE);
|
|||
*/
|
||||
PyAPI_FUNC(PyThreadState *) PyGILState_GetThisThreadState(void);
|
||||
|
||||
/* The implementation of sys._current_frames() Returns a dict mapping
|
||||
thread id to that thread's current frame.
|
||||
*/
|
||||
PyAPI_FUNC(PyObject *) _PyThread_CurrentFrames(void);
|
||||
|
||||
/* Routines for advanced debuggers, requested by David Beazley.
|
||||
Don't use unless you know what you are doing! */
|
||||
PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Head(void);
|
||||
|
|
|
@ -237,6 +237,67 @@ class SysModuleTest(unittest.TestCase):
|
|||
is sys._getframe().f_code
|
||||
)
|
||||
|
||||
# sys._current_frames() is a CPython-only gimmick.
|
||||
def test_current_frames(self):
|
||||
import threading, thread
|
||||
import traceback
|
||||
|
||||
# Spawn a thread that blocks at a known place. Then the main
|
||||
# thread does sys._current_frames(), and verifies that the frames
|
||||
# returned make sense.
|
||||
entered_g = threading.Event()
|
||||
leave_g = threading.Event()
|
||||
thread_info = [] # the thread's id
|
||||
|
||||
def f123():
|
||||
g456()
|
||||
|
||||
def g456():
|
||||
thread_info.append(thread.get_ident())
|
||||
entered_g.set()
|
||||
leave_g.wait()
|
||||
|
||||
t = threading.Thread(target=f123)
|
||||
t.start()
|
||||
entered_g.wait()
|
||||
|
||||
# At this point, t has finished its entered_g.set(), and is blocked
|
||||
# in its leave_g.wait().
|
||||
self.assertEqual(len(thread_info), 1)
|
||||
thread_id = thread_info[0]
|
||||
|
||||
d = sys._current_frames()
|
||||
|
||||
main_id = thread.get_ident()
|
||||
self.assert_(main_id in d)
|
||||
self.assert_(thread_id in d)
|
||||
|
||||
# Verify that the captured main-thread frame is _this_ frame.
|
||||
frame = d.pop(main_id)
|
||||
self.assert_(frame is sys._getframe())
|
||||
|
||||
# Verify that the captured thread frame is blocked in g456, called
|
||||
# from f123. This is a litte tricky, since various bits of
|
||||
# threading.py are also in the thread's call stack.
|
||||
frame = d.pop(thread_id)
|
||||
stack = traceback.extract_stack(frame)
|
||||
for i, (filename, lineno, funcname, sourceline) in enumerate(stack):
|
||||
if funcname == "f123":
|
||||
break
|
||||
else:
|
||||
self.fail("didn't find f123() on thread's call stack")
|
||||
|
||||
self.assertEqual(sourceline, "g456()")
|
||||
|
||||
# And the next record must be for g456().
|
||||
filename, lineno, funcname, sourceline = stack[i+1]
|
||||
self.assertEqual(funcname, "g456")
|
||||
self.assertEqual(sourceline, "leave_g.wait()")
|
||||
|
||||
# Reap the spawned thread.
|
||||
leave_g.set()
|
||||
t.join()
|
||||
|
||||
def test_attributes(self):
|
||||
self.assert_(isinstance(sys.api_version, int))
|
||||
self.assert_(isinstance(sys.argv, list))
|
||||
|
|
11
Misc/NEWS
11
Misc/NEWS
|
@ -36,10 +36,17 @@ Core and builtins
|
|||
- Bug #1512814, Fix incorrect lineno's when code at module scope
|
||||
started after line 256.
|
||||
|
||||
- New function ``sys._current_frames()`` returns a dict mapping thread
|
||||
id to topmost thread stack frame. This is for expert use, and is
|
||||
especially useful for debugging application deadlocks. The functionality
|
||||
was previously available in Fazal Majid's ``threadframe`` extension
|
||||
module, but it wasn't possible to do this in a wholly threadsafe way from
|
||||
an extension.
|
||||
|
||||
Library
|
||||
-------
|
||||
|
||||
- Bug #1508010: msvccompiler now requires the DISTUTILS_USE_SDK
|
||||
- Bug #1508010: msvccompiler now requires the DISTUTILS_USE_SDK
|
||||
environment variable to be set in order to the SDK environment
|
||||
for finding the compiler, include files, etc.
|
||||
|
||||
|
@ -126,7 +133,7 @@ Extension Modules
|
|||
Build
|
||||
-----
|
||||
|
||||
- 'configure' now detects the zlib library the same way as distutils.
|
||||
- 'configure' now detects the zlib library the same way as distutils.
|
||||
Previously, the slight difference could cause compilation errors of the
|
||||
'zlib' module on systems with more than one version of zlib.
|
||||
|
||||
|
|
|
@ -444,15 +444,15 @@ _PyGILState_NoteThreadState(PyThreadState* tstate)
|
|||
/* If autoTLSkey is 0, this must be the very first threadstate created
|
||||
in Py_Initialize(). Don't do anything for now (we'll be back here
|
||||
when _PyGILState_Init is called). */
|
||||
if (!autoTLSkey)
|
||||
if (!autoTLSkey)
|
||||
return;
|
||||
|
||||
|
||||
/* Stick the thread state for this thread in thread local storage.
|
||||
|
||||
The only situation where you can legitimately have more than one
|
||||
thread state for an OS level thread is when there are multiple
|
||||
interpreters, when:
|
||||
|
||||
|
||||
a) You shouldn't really be using the PyGILState_ APIs anyway,
|
||||
and:
|
||||
|
||||
|
@ -550,6 +550,54 @@ PyGILState_Release(PyGILState_STATE oldstate)
|
|||
PyEval_SaveThread();
|
||||
}
|
||||
|
||||
/* The implementation of sys._current_frames(). This is intended to be
|
||||
called with the GIL held, as it will be when called via
|
||||
sys._current_frames(). It's possible it would work fine even without
|
||||
the GIL held, but haven't thought enough about that.
|
||||
*/
|
||||
PyObject *
|
||||
_PyThread_CurrentFrames(void)
|
||||
{
|
||||
PyObject *result;
|
||||
PyInterpreterState *i;
|
||||
|
||||
result = PyDict_New();
|
||||
if (result == NULL)
|
||||
return NULL;
|
||||
|
||||
/* for i in all interpreters:
|
||||
* for t in all of i's thread states:
|
||||
* if t's frame isn't NULL, map t's id to its frame
|
||||
* Because these lists can mutute even when the GIL is held, we
|
||||
* need to grab head_mutex for the duration.
|
||||
*/
|
||||
HEAD_LOCK();
|
||||
for (i = interp_head; i != NULL; i = i->next) {
|
||||
PyThreadState *t;
|
||||
for (t = i->tstate_head; t != NULL; t = t->next) {
|
||||
PyObject *id;
|
||||
int stat;
|
||||
struct _frame *frame = t->frame;
|
||||
if (frame == NULL)
|
||||
continue;
|
||||
id = PyInt_FromLong(t->thread_id);
|
||||
if (id == NULL)
|
||||
goto Fail;
|
||||
stat = PyDict_SetItem(result, id, (PyObject *)frame);
|
||||
Py_DECREF(id);
|
||||
if (stat < 0)
|
||||
goto Fail;
|
||||
}
|
||||
}
|
||||
HEAD_UNLOCK();
|
||||
return result;
|
||||
|
||||
Fail:
|
||||
HEAD_UNLOCK();
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -660,6 +660,21 @@ sys_getframe(PyObject *self, PyObject *args)
|
|||
return (PyObject*)f;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(current_frames_doc,
|
||||
"_current_frames() -> dictionary\n\
|
||||
\n\
|
||||
Return a dictionary mapping each current thread T's thread id to T's\n\
|
||||
current stack frame.\n\
|
||||
\n\
|
||||
This function should be used for specialized purposes only."
|
||||
);
|
||||
|
||||
static PyObject *
|
||||
sys_current_frames(PyObject *self, PyObject *noargs)
|
||||
{
|
||||
return _PyThread_CurrentFrames();
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(call_tracing_doc,
|
||||
"call_tracing(func, args) -> object\n\
|
||||
\n\
|
||||
|
@ -722,6 +737,8 @@ static PyMethodDef sys_methods[] = {
|
|||
/* Might as well keep this in alphabetic order */
|
||||
{"callstats", (PyCFunction)PyEval_GetCallStats, METH_NOARGS,
|
||||
callstats_doc},
|
||||
{"_current_frames", sys_current_frames, METH_NOARGS,
|
||||
current_frames_doc},
|
||||
{"displayhook", sys_displayhook, METH_O, displayhook_doc},
|
||||
{"exc_info", sys_exc_info, METH_NOARGS, exc_info_doc},
|
||||
{"exc_clear", sys_exc_clear, METH_NOARGS, exc_clear_doc},
|
||||
|
|
Loading…
Reference in New Issue