mirror of
https://github.com/python/cpython.git
synced 2025-07-31 23:23:11 +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.
|
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.
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -36,6 +36,13 @@ 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
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -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