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
|
@ -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.
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -36,6 +36,13 @@ 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
|
||||
-------
|
||||
|
||||
|
|
|
@ -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…
Add table
Add a link
Reference in a new issue