mirror of
https://github.com/python/cpython.git
synced 2025-07-29 14:15:07 +00:00
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
6 changed files with 166 additions and 11 deletions
|
@ -41,7 +41,7 @@ It is always available.
|
||||||
\code{Include/patchlevel.h} if the branch is a tag. Otherwise,
|
\code{Include/patchlevel.h} if the branch is a tag. Otherwise,
|
||||||
it is \code{None}.
|
it is \code{None}.
|
||||||
\versionadded{2.5}
|
\versionadded{2.5}
|
||||||
\end{datadesc}
|
\end{datadesc}
|
||||||
|
|
||||||
\begin{datadesc}{builtin_module_names}
|
\begin{datadesc}{builtin_module_names}
|
||||||
A tuple of strings giving the names of all modules that are compiled
|
A tuple of strings giving the names of all modules that are compiled
|
||||||
|
@ -55,6 +55,23 @@ It is always available.
|
||||||
interpreter.
|
interpreter.
|
||||||
\end{datadesc}
|
\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}
|
\begin{datadesc}{dllhandle}
|
||||||
Integer specifying the handle of the Python DLL.
|
Integer specifying the handle of the Python DLL.
|
||||||
Availability: Windows.
|
Availability: Windows.
|
||||||
|
@ -142,7 +159,7 @@ It is always available.
|
||||||
function, \function{exc_info()} will return three \code{None} values until
|
function, \function{exc_info()} will return three \code{None} values until
|
||||||
another exception is raised in the current thread or the execution stack
|
another exception is raised in the current thread or the execution stack
|
||||||
returns to a frame where another exception is being handled.
|
returns to a frame where another exception is being handled.
|
||||||
|
|
||||||
This function is only needed in only a few obscure situations. These
|
This function is only needed in only a few obscure situations. These
|
||||||
include logging and error handling systems that report information on the
|
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
|
last or current exception. This function can also be used to try to free
|
||||||
|
@ -241,7 +258,7 @@ It is always available.
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item On Windows 9x, the encoding is ``mbcs''.
|
\item On Windows 9x, the encoding is ``mbcs''.
|
||||||
\item On Mac OS X, the encoding is ``utf-8''.
|
\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
|
according to the result of nl_langinfo(CODESET), or None if
|
||||||
the nl_langinfo(CODESET) failed.
|
the nl_langinfo(CODESET) failed.
|
||||||
\item On Windows NT+, file names are Unicode natively, so no conversion
|
\item On Windows NT+, file names are Unicode natively, so no conversion
|
||||||
|
@ -279,8 +296,8 @@ It is always available.
|
||||||
\end{funcdesc}
|
\end{funcdesc}
|
||||||
|
|
||||||
\begin{funcdesc}{getwindowsversion}{}
|
\begin{funcdesc}{getwindowsversion}{}
|
||||||
Return a tuple containing five components, describing the Windows
|
Return a tuple containing five components, describing the Windows
|
||||||
version currently running. The elements are \var{major}, \var{minor},
|
version currently running. The elements are \var{major}, \var{minor},
|
||||||
\var{build}, \var{platform}, and \var{text}. \var{text} contains
|
\var{build}, \var{platform}, and \var{text}. \var{text} contains
|
||||||
a string while all other values are integers.
|
a string while all other values are integers.
|
||||||
|
|
||||||
|
@ -491,7 +508,7 @@ else:
|
||||||
be registered using \function{settrace()} for each thread being
|
be registered using \function{settrace()} for each thread being
|
||||||
debugged. \note{The \function{settrace()} function is intended only
|
debugged. \note{The \function{settrace()} function is intended only
|
||||||
for implementing debuggers, profilers, coverage tools and the like.
|
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
|
part of the language definition, and thus may not be available in
|
||||||
all Python implementations.}
|
all Python implementations.}
|
||||||
\end{funcdesc}
|
\end{funcdesc}
|
||||||
|
|
|
@ -171,6 +171,11 @@ PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE);
|
||||||
*/
|
*/
|
||||||
PyAPI_FUNC(PyThreadState *) PyGILState_GetThisThreadState(void);
|
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.
|
/* Routines for advanced debuggers, requested by David Beazley.
|
||||||
Don't use unless you know what you are doing! */
|
Don't use unless you know what you are doing! */
|
||||||
PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Head(void);
|
PyAPI_FUNC(PyInterpreterState *) PyInterpreterState_Head(void);
|
||||||
|
|
|
@ -237,6 +237,67 @@ class SysModuleTest(unittest.TestCase):
|
||||||
is sys._getframe().f_code
|
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):
|
def test_attributes(self):
|
||||||
self.assert_(isinstance(sys.api_version, int))
|
self.assert_(isinstance(sys.api_version, int))
|
||||||
self.assert_(isinstance(sys.argv, list))
|
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
|
- Bug #1512814, Fix incorrect lineno's when code at module scope
|
||||||
started after line 256.
|
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
|
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
|
environment variable to be set in order to the SDK environment
|
||||||
for finding the compiler, include files, etc.
|
for finding the compiler, include files, etc.
|
||||||
|
|
||||||
|
@ -126,7 +133,7 @@ Extension Modules
|
||||||
Build
|
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
|
Previously, the slight difference could cause compilation errors of the
|
||||||
'zlib' module on systems with more than one version of zlib.
|
'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
|
/* 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
|
in Py_Initialize(). Don't do anything for now (we'll be back here
|
||||||
when _PyGILState_Init is called). */
|
when _PyGILState_Init is called). */
|
||||||
if (!autoTLSkey)
|
if (!autoTLSkey)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Stick the thread state for this thread in thread local storage.
|
/* Stick the thread state for this thread in thread local storage.
|
||||||
|
|
||||||
The only situation where you can legitimately have more than one
|
The only situation where you can legitimately have more than one
|
||||||
thread state for an OS level thread is when there are multiple
|
thread state for an OS level thread is when there are multiple
|
||||||
interpreters, when:
|
interpreters, when:
|
||||||
|
|
||||||
a) You shouldn't really be using the PyGILState_ APIs anyway,
|
a) You shouldn't really be using the PyGILState_ APIs anyway,
|
||||||
and:
|
and:
|
||||||
|
|
||||||
|
@ -550,6 +550,54 @@ PyGILState_Release(PyGILState_STATE oldstate)
|
||||||
PyEval_SaveThread();
|
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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -660,6 +660,21 @@ sys_getframe(PyObject *self, PyObject *args)
|
||||||
return (PyObject*)f;
|
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,
|
PyDoc_STRVAR(call_tracing_doc,
|
||||||
"call_tracing(func, args) -> object\n\
|
"call_tracing(func, args) -> object\n\
|
||||||
\n\
|
\n\
|
||||||
|
@ -722,6 +737,8 @@ static PyMethodDef sys_methods[] = {
|
||||||
/* Might as well keep this in alphabetic order */
|
/* Might as well keep this in alphabetic order */
|
||||||
{"callstats", (PyCFunction)PyEval_GetCallStats, METH_NOARGS,
|
{"callstats", (PyCFunction)PyEval_GetCallStats, METH_NOARGS,
|
||||||
callstats_doc},
|
callstats_doc},
|
||||||
|
{"_current_frames", sys_current_frames, METH_NOARGS,
|
||||||
|
current_frames_doc},
|
||||||
{"displayhook", sys_displayhook, METH_O, displayhook_doc},
|
{"displayhook", sys_displayhook, METH_O, displayhook_doc},
|
||||||
{"exc_info", sys_exc_info, METH_NOARGS, exc_info_doc},
|
{"exc_info", sys_exc_info, METH_NOARGS, exc_info_doc},
|
||||||
{"exc_clear", sys_exc_clear, METH_NOARGS, exc_clear_doc},
|
{"exc_clear", sys_exc_clear, METH_NOARGS, exc_clear_doc},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue