Kaydet (Commit) 32a8361f authored tarafından Tim Peters's avatar Tim Peters

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.
üst 2b221ed6
......@@ -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},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment